diff --git a/.mockery.yaml b/.mockery.yaml index ddac60b5..40897176 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -48,6 +48,9 @@ packages: LogService: config: filename: "log_service.go" + NotificationService: + config: + filename: "notification_service.go" github.com/goto/siren/core/log: config: dir: "core/log/mocks" @@ -99,6 +102,9 @@ packages: Dispatcher: config: filename: "dispatcher.go" + Router: + config: + filename: "router.go" SubscriptionService: config: filename: "subscription_service.go" @@ -108,9 +114,9 @@ packages: SilenceService: config: filename: "silence_service.go" - AlertService: + AlertRepository: config: - filename: "alert_service.go" + filename: "alert_repository.go" LogService: config: filename: "log_service.go" diff --git a/Makefile b/Makefile index 3b2b6bba..fc38b562 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ NAME="github.com/goto/siren" LAST_COMMIT := $(shell git rev-parse --short HEAD) LAST_TAG := "$(shell git rev-list --tags --max-count=1)" APP_VERSION := "$(shell git describe --tags ${LAST_TAG})-next" -PROTON_COMMIT := "478369b80e67ec3c98464050e51f47332d915962" +PROTON_COMMIT := "ebb3a512fc98f28b3b2933da022d04c093673106" .PHONY: all build test clean dist vet proto install diff --git a/cli/deps.go b/cli/deps.go index 3491d547..ebb9e9eb 100644 --- a/cli/deps.go +++ b/cli/deps.go @@ -90,13 +90,6 @@ func InitDeps( } } - alertRepository := postgres.NewAlertRepository(pgClient) - alertService := alert.NewService( - alertRepository, - logService, - alertTransformers, - ) - namespaceRepository := postgres.NewNamespaceRepository(pgClient) namespaceService := namespace.NewService(encryptor, namespaceRepository, providerService, configSyncers) @@ -143,6 +136,24 @@ func InitDeps( ) // notification + idempotencyRepository := postgres.NewIdempotencyRepository(pgClient) + notificationRepository := postgres.NewNotificationRepository(pgClient) + alertRepository := postgres.NewAlertRepository(pgClient) + + notificationDeps := notification.Deps{ + Logger: logger, + Cfg: cfg.Notification, + Repository: notificationRepository, + Q: queue, + LogService: logService, + IdempotencyRepository: idempotencyRepository, + AlertRepository: alertRepository, + ReceiverService: receiverService, + SubscriptionService: subscriptionService, + SilenceService: silenceService, + TemplateService: templateService, + } + notifierRegistry := map[string]notification.Notifier{ receiver.TypeSlack: slackPluginService, receiver.TypeSlackChannel: slackChannelPluginService, @@ -151,25 +162,42 @@ func InitDeps( receiver.TypeFile: filePluginService, } - idempotencyRepository := postgres.NewIdempotencyRepository(pgClient) - notificationRepository := postgres.NewNotificationRepository(pgClient) + routerRegistry := map[string]notification.Router{ + notification.RouterReceiver: notification.NewRouterReceiverService( + notificationDeps, + notifierRegistry, + ), + notification.RouterSubscriber: notification.NewRouterSubscriberService( + notificationDeps, + notifierRegistry, + ), + } + + dispatchServiceRegistry := map[string]notification.Dispatcher{ + notification.DispatchKindBulkNotification: notification.NewDispatchBulkNotificationService( + notificationDeps, + notifierRegistry, + routerRegistry, + ), + notification.DispatchKindSingleNotification: notification.NewDispatchSingleNotificationService( + notificationDeps, + notifierRegistry, + routerRegistry, + ), + } notificationService := notification.NewService( + notificationDeps, + dispatchServiceRegistry, + ) + + alertService := alert.NewService( + cfg.Alert, logger, - cfg.Notification, - notificationRepository, - queue, - notifierRegistry, - notification.Deps{ - LogService: logService, - IdempotencyRepository: idempotencyRepository, - ReceiverService: receiverService, - SubscriptionService: subscriptionService, - SilenceService: silenceService, - AlertService: alertService, - TemplateService: templateService, - }, - cfg.Service.EnableSilenceFeature, + alertRepository, + logService, + notificationService, + alertTransformers, ) return &api.Deps{ diff --git a/config/config.go b/config/config.go index 412e4393..14d736ec 100644 --- a/config/config.go +++ b/config/config.go @@ -7,6 +7,7 @@ import ( "github.com/goto/salt/config" "github.com/goto/salt/db" "github.com/goto/salt/telemetry" + "github.com/goto/siren/core/alert" "github.com/goto/siren/core/notification" "github.com/goto/siren/internal/server" "github.com/goto/siren/pkg/errors" @@ -44,4 +45,5 @@ type Config struct { Providers plugins.Config `mapstructure:"providers" yaml:"providers"` Receivers receivers.Config `mapstructure:"receivers" yaml:"receivers"` Notification notification.Config `mapstructure:"notification" yaml:"notification"` + Alert alert.Config `mapstructure:"alert" yaml:"alert"` } diff --git a/core/alert/config.go b/core/alert/config.go new file mode 100644 index 00000000..70b8e13a --- /dev/null +++ b/core/alert/config.go @@ -0,0 +1,5 @@ +package alert + +type Config struct { + GroupBy []string `mapstructure:"group_by" yaml:"group_by"` +} diff --git a/core/alert/mocks/alert_repository.go b/core/alert/mocks/alert_repository.go index 688abe8c..ec0327d6 100644 --- a/core/alert/mocks/alert_repository.go +++ b/core/alert/mocks/alert_repository.go @@ -71,6 +71,52 @@ func (_c *Repository_BulkUpdateSilence_Call) RunAndReturn(run func(context.Conte return _c } +// Commit provides a mock function with given fields: ctx +func (_m *Repository) Commit(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Commit") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Repository_Commit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Commit' +type Repository_Commit_Call struct { + *mock.Call +} + +// Commit is a helper method to define mock.On call +// - ctx context.Context +func (_e *Repository_Expecter) Commit(ctx interface{}) *Repository_Commit_Call { + return &Repository_Commit_Call{Call: _e.mock.On("Commit", ctx)} +} + +func (_c *Repository_Commit_Call) Run(run func(ctx context.Context)) *Repository_Commit_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Repository_Commit_Call) Return(_a0 error) *Repository_Commit_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Repository_Commit_Call) RunAndReturn(run func(context.Context) error) *Repository_Commit_Call { + _c.Call.Return(run) + return _c +} + // Create provides a mock function with given fields: _a0, _a1 func (_m *Repository) Create(_a0 context.Context, _a1 alert.Alert) (alert.Alert, error) { ret := _m.Called(_a0, _a1) @@ -187,6 +233,101 @@ func (_c *Repository_List_Call) RunAndReturn(run func(context.Context, alert.Fil return _c } +// Rollback provides a mock function with given fields: ctx, err +func (_m *Repository) Rollback(ctx context.Context, err error) error { + ret := _m.Called(ctx, err) + + if len(ret) == 0 { + panic("no return value specified for Rollback") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, error) error); ok { + r0 = rf(ctx, err) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Repository_Rollback_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Rollback' +type Repository_Rollback_Call struct { + *mock.Call +} + +// Rollback is a helper method to define mock.On call +// - ctx context.Context +// - err error +func (_e *Repository_Expecter) Rollback(ctx interface{}, err interface{}) *Repository_Rollback_Call { + return &Repository_Rollback_Call{Call: _e.mock.On("Rollback", ctx, err)} +} + +func (_c *Repository_Rollback_Call) Run(run func(ctx context.Context, err error)) *Repository_Rollback_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(error)) + }) + return _c +} + +func (_c *Repository_Rollback_Call) Return(_a0 error) *Repository_Rollback_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Repository_Rollback_Call) RunAndReturn(run func(context.Context, error) error) *Repository_Rollback_Call { + _c.Call.Return(run) + return _c +} + +// WithTransaction provides a mock function with given fields: ctx +func (_m *Repository) WithTransaction(ctx context.Context) context.Context { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for WithTransaction") + } + + var r0 context.Context + if rf, ok := ret.Get(0).(func(context.Context) context.Context); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(context.Context) + } + } + + return r0 +} + +// Repository_WithTransaction_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WithTransaction' +type Repository_WithTransaction_Call struct { + *mock.Call +} + +// WithTransaction is a helper method to define mock.On call +// - ctx context.Context +func (_e *Repository_Expecter) WithTransaction(ctx interface{}) *Repository_WithTransaction_Call { + return &Repository_WithTransaction_Call{Call: _e.mock.On("WithTransaction", ctx)} +} + +func (_c *Repository_WithTransaction_Call) Run(run func(ctx context.Context)) *Repository_WithTransaction_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Repository_WithTransaction_Call) Return(_a0 context.Context) *Repository_WithTransaction_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Repository_WithTransaction_Call) RunAndReturn(run func(context.Context) context.Context) *Repository_WithTransaction_Call { + _c.Call.Return(run) + return _c +} + // NewRepository creates a new instance of Repository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewRepository(t interface { diff --git a/core/alert/mocks/notification_service.go b/core/alert/mocks/notification_service.go new file mode 100644 index 00000000..0f726a9d --- /dev/null +++ b/core/alert/mocks/notification_service.go @@ -0,0 +1,97 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + notification "github.com/goto/siren/core/notification" + mock "github.com/stretchr/testify/mock" +) + +// NotificationService is an autogenerated mock type for the NotificationService type +type NotificationService struct { + mock.Mock +} + +type NotificationService_Expecter struct { + mock *mock.Mock +} + +func (_m *NotificationService) EXPECT() *NotificationService_Expecter { + return &NotificationService_Expecter{mock: &_m.Mock} +} + +// Dispatch provides a mock function with given fields: _a0, _a1, _a2 +func (_m *NotificationService) Dispatch(_a0 context.Context, _a1 []notification.Notification, _a2 string) ([]string, error) { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for Dispatch") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []notification.Notification, string) ([]string, error)); ok { + return rf(_a0, _a1, _a2) + } + if rf, ok := ret.Get(0).(func(context.Context, []notification.Notification, string) []string); ok { + r0 = rf(_a0, _a1, _a2) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []notification.Notification, string) error); ok { + r1 = rf(_a0, _a1, _a2) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NotificationService_Dispatch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Dispatch' +type NotificationService_Dispatch_Call struct { + *mock.Call +} + +// Dispatch is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 []notification.Notification +// - _a2 string +func (_e *NotificationService_Expecter) Dispatch(_a0 interface{}, _a1 interface{}, _a2 interface{}) *NotificationService_Dispatch_Call { + return &NotificationService_Dispatch_Call{Call: _e.mock.On("Dispatch", _a0, _a1, _a2)} +} + +func (_c *NotificationService_Dispatch_Call) Run(run func(_a0 context.Context, _a1 []notification.Notification, _a2 string)) *NotificationService_Dispatch_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].([]notification.Notification), args[2].(string)) + }) + return _c +} + +func (_c *NotificationService_Dispatch_Call) Return(_a0 []string, _a1 error) *NotificationService_Dispatch_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *NotificationService_Dispatch_Call) RunAndReturn(run func(context.Context, []notification.Notification, string) ([]string, error)) *NotificationService_Dispatch_Call { + _c.Call.Return(run) + return _c +} + +// NewNotificationService creates a new instance of NotificationService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewNotificationService(t interface { + mock.TestingT + Cleanup(func()) +}) *NotificationService { + mock := &NotificationService{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/alert/service.go b/core/alert/service.go index 0b254634..7aef1b43 100644 --- a/core/alert/service.go +++ b/core/alert/service.go @@ -2,50 +2,79 @@ package alert import ( "context" + "fmt" "time" + saltlog "github.com/goto/salt/log" + "github.com/goto/siren/core/notification" + "github.com/goto/siren/core/template" "github.com/goto/siren/pkg/errors" + "github.com/goto/siren/pkg/structure" ) type LogService interface { ListAlertIDsBySilenceID(ctx context.Context, silenceID string) ([]int64, error) } +type NotificationService interface { + Dispatch(context.Context, []notification.Notification, string) ([]string, error) +} + // Service handles business logic type Service struct { - repository Repository - logService LogService - registry map[string]AlertTransformer + cfg Config + logger saltlog.Logger + repository Repository + logService LogService + notificationService NotificationService + registry map[string]AlertTransformer } // NewService returns repository struct -func NewService(repository Repository, logService LogService, registry map[string]AlertTransformer) *Service { - return &Service{repository, logService, registry} +func NewService(cfg Config, logger saltlog.Logger, repository Repository, logService LogService, notificationService NotificationService, registry map[string]AlertTransformer) *Service { + return &Service{cfg, logger, repository, logService, notificationService, registry} } -func (s *Service) CreateAlerts(ctx context.Context, providerType string, providerID uint64, namespaceID uint64, body map[string]any) ([]Alert, int, error) { +func (s *Service) CreateAlerts(ctx context.Context, providerType string, providerID uint64, namespaceID uint64, body map[string]any) ([]Alert, error) { pluginService, err := s.getProviderPluginService(providerType) if err != nil { - return nil, 0, err + return nil, err } - alerts, firingLen, err := pluginService.TransformToAlerts(ctx, providerID, namespaceID, body) if err != nil { - return nil, 0, err + return nil, err } for i := 0; i < len(alerts); i++ { createdAlert, err := s.repository.Create(ctx, alerts[i]) if err != nil { if errors.Is(err, ErrRelation) { - return nil, 0, errors.ErrNotFound.WithMsgf(err.Error()) + return nil, errors.ErrNotFound.WithMsgf(err.Error()) } - return nil, 0, err + return nil, err } alerts[i].ID = createdAlert.ID } - return alerts, firingLen, nil + if len(alerts) > 0 { + // Publish to notification service + ns, err := BuildNotifications(alerts, firingLen, time.Now(), s.cfg.GroupBy) + if err != nil { + s.logger.Warn("failed to build notifications from alert", "err", err, "alerts", alerts) + } + + // failure on dispatch won't rollback the db changes + for _, n := range ns { + if _, err := s.notificationService.Dispatch(ctx, []notification.Notification{n}, notification.DispatchKindSingleNotification); err != nil { + s.logger.Warn("failed to send alert as notification", "err", err, "notification", n) + } + } + + } else { + s.logger.Warn("failed to send alert as notification, empty created alerts") + } + + return alerts, nil } func (s *Service) List(ctx context.Context, flt Filter) ([]Alert, error) { @@ -64,10 +93,6 @@ func (s *Service) List(ctx context.Context, flt Filter) ([]Alert, error) { return s.repository.List(ctx, flt) } -func (s *Service) UpdateSilenceStatus(ctx context.Context, alertIDs []int64, hasSilenced bool, hasNonSilenced bool) error { - return s.repository.BulkUpdateSilence(ctx, alertIDs, silenceStatus(hasSilenced, hasNonSilenced)) -} - func (s *Service) getProviderPluginService(providerType string) (AlertTransformer, error) { pluginService, exist := s.registry[providerType] if !exist { @@ -75,3 +100,92 @@ func (s *Service) getProviderPluginService(providerType string) (AlertTransforme } return pluginService, nil } + +// Transform alerts and populate Data and Labels to be interpolated to the system-default template +// .Data +// - id +// - status "FIRING"/"RESOLVED" +// - resource +// - template +// - metric_value +// - metric_name +// - generator_url +// - num_alerts_firing +// - dashboard +// - playbook +// - summary +// .Labels +// - severity "WARNING"/"CRITICAL" +// - alertname +// - (others labels defined in rules) +func BuildNotifications( + alerts []Alert, + firingLen int, + createdTime time.Time, + groupByLabels []string, +) ([]notification.Notification, error) { + if len(alerts) == 0 { + return nil, errors.New("empty alerts") + } + + alertsMap, err := structure.GroupByLabels(alerts, groupByLabels, func(a Alert) map[string]string { return a.Labels }) + if err != nil { + return nil, err + } + + var notifications []notification.Notification + + for hashKey, groupedAlerts := range alertsMap { + sampleAlert := groupedAlerts[0] + + data := map[string]any{} + + mergedAnnotations := map[string][]string{} + for _, a := range groupedAlerts { + for k, v := range a.Annotations { + mergedAnnotations[k] = append(mergedAnnotations[k], v) + } + } + // make unique + for k, v := range mergedAnnotations { + mergedAnnotations[k] = structure.RemoveDuplicate(v) + } + // render annotations + for k, vSlice := range mergedAnnotations { + for _, v := range vSlice { + if _, ok := data[k]; ok { + data[k] = fmt.Sprintf("%s\n%s", data[k], v) + } else { + data[k] = v + } + } + } + + data["status"] = sampleAlert.Status + data["generator_url"] = sampleAlert.GeneratorURL + data["num_alerts_firing"] = firingLen + + alertIDs := []int64{} + + for _, a := range groupedAlerts { + alertIDs = append(alertIDs, int64(a.ID)) + } + + for k, v := range sampleAlert.Labels { + data[k] = v + } + + notifications = append(notifications, notification.Notification{ + NamespaceID: sampleAlert.NamespaceID, + Type: notification.TypeAlert, + Data: data, + Labels: sampleAlert.Labels, + Template: template.ReservedName_SystemDefault, + UniqueKey: structure.HashGroupKey(sampleAlert.GroupKey, hashKey), + CreatedAt: createdTime, + AlertIDs: alertIDs, + }) + } + + return notifications, nil +} diff --git a/core/alert/service_test.go b/core/alert/service_test.go index 2cd761e4..3dae266c 100644 --- a/core/alert/service_test.go +++ b/core/alert/service_test.go @@ -6,8 +6,12 @@ import ( "time" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + saltlog "github.com/goto/salt/log" "github.com/goto/siren/core/alert" "github.com/goto/siren/core/alert/mocks" + "github.com/goto/siren/core/notification" + "github.com/goto/siren/core/template" "github.com/goto/siren/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -18,7 +22,7 @@ func TestService_List(t *testing.T) { t.Run("should call repository List method with proper arguments and return result in domain's type", func(t *testing.T) { repositoryMock := &mocks.Repository{} - dummyService := alert.NewService(repositoryMock, nil, nil) + dummyService := alert.NewService(alert.Config{}, saltlog.NewNoop(), repositoryMock, nil, nil, nil) timenow := time.Now() dummyAlerts := []alert.Alert{ {ID: 1, ProviderID: 1, ResourceName: "foo", Severity: "CRITICAL", MetricName: "baz", MetricValue: "20", @@ -45,7 +49,7 @@ func TestService_List(t *testing.T) { t.Run("should call repository List method with proper arguments if endtime is zero", func(t *testing.T) { repositoryMock := &mocks.Repository{} - dummyService := alert.NewService(repositoryMock, nil, nil) + dummyService := alert.NewService(alert.Config{}, saltlog.NewNoop(), repositoryMock, nil, nil, nil) timenow := time.Now() dummyAlerts := []alert.Alert{ {ID: 1, ProviderID: 1, ResourceName: "foo", Severity: "CRITICAL", MetricName: "baz", MetricValue: "20", @@ -67,7 +71,7 @@ func TestService_List(t *testing.T) { t.Run("should call repository List method and handle errors", func(t *testing.T) { repositoryMock := &mocks.Repository{} - dummyService := alert.NewService(repositoryMock, nil, nil) + dummyService := alert.NewService(alert.Config{}, saltlog.NewNoop(), repositoryMock, nil, nil, nil) repositoryMock.EXPECT().List(mock.AnythingOfType("context.todoCtx"), mock.Anything). Return(nil, errors.New("random error")) actualAlerts, err := dummyService.List(ctx, alert.Filter{ @@ -107,7 +111,7 @@ func TestService_CreateAlerts(t *testing.T) { var testCases = []struct { name string - setup func(*mocks.Repository, *mocks.AlertTransformer) + setup func(*mocks.Repository, *mocks.AlertTransformer, *mocks.NotificationService) alertsToBeCreated map[string]any expectedAlerts []alert.Alert expectedFiringLen int @@ -115,7 +119,8 @@ func TestService_CreateAlerts(t *testing.T) { }{ { name: "should return error if TransformToAlerts return error", - setup: func(ar *mocks.Repository, at *mocks.AlertTransformer) { + setup: func(ar *mocks.Repository, at *mocks.AlertTransformer, ns *mocks.NotificationService) { + ar.EXPECT().WithTransaction(mock.AnythingOfType("context.todoCtx")).Return(ctx) at.EXPECT().TransformToAlerts(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("uint64"), mock.AnythingOfType("uint64"), mock.AnythingOfType("map[string]interface {}")).Return(nil, 0, errors.New("some error")) }, alertsToBeCreated: alertsToBeCreated, @@ -123,13 +128,16 @@ func TestService_CreateAlerts(t *testing.T) { }, { name: "should call repository Create method with proper arguments", - setup: func(ar *mocks.Repository, at *mocks.AlertTransformer) { + setup: func(ar *mocks.Repository, at *mocks.AlertTransformer, ns *mocks.NotificationService) { + ar.EXPECT().WithTransaction(mock.AnythingOfType("context.todoCtx")).Return(ctx) at.EXPECT().TransformToAlerts(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("uint64"), mock.AnythingOfType("uint64"), mock.AnythingOfType("map[string]interface {}")).Return([]alert.Alert{ {ID: 1, ProviderID: 1, ResourceName: "foo", Severity: "CRITICAL", MetricName: "lag", MetricValue: "20", Rule: "lagHigh", TriggeredAt: timenow}, }, 1, nil) ar.EXPECT().Create(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("alert.Alert")).Return(alert.Alert{ID: 1, ProviderID: 1, ResourceName: "foo", Severity: "CRITICAL", MetricName: "lag", MetricValue: "20", Rule: "lagHigh", TriggeredAt: timenow}, nil) + ns.EXPECT().Dispatch(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("[]notification.Notification"), mock.AnythingOfType("string")).Return(nil, nil) + ar.EXPECT().Commit(mock.AnythingOfType("context.todoCtx")).Return(nil) }, alertsToBeCreated: alertsToBeCreated, expectedFiringLen: 1, @@ -140,24 +148,29 @@ func TestService_CreateAlerts(t *testing.T) { }, { name: "should return error not found if repository return err relation", - setup: func(ar *mocks.Repository, at *mocks.AlertTransformer) { + setup: func(ar *mocks.Repository, at *mocks.AlertTransformer, ns *mocks.NotificationService) { + ar.EXPECT().WithTransaction(mock.AnythingOfType("context.todoCtx")).Return(ctx) at.EXPECT().TransformToAlerts(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("uint64"), mock.AnythingOfType("uint64"), mock.AnythingOfType("map[string]interface {}")).Return([]alert.Alert{ {ID: 1, ProviderID: 1, ResourceName: "foo", Severity: "CRITICAL", MetricName: "lag", MetricValue: "20", Rule: "lagHigh", TriggeredAt: timenow}, }, 1, nil) ar.EXPECT().Create(mock.AnythingOfType("context.todoCtx"), mock.Anything).Return(alert.Alert{}, alert.ErrRelation) + ar.EXPECT().Rollback(mock.AnythingOfType("context.todoCtx"), errors.ErrNotFound.WithMsgf(alert.ErrRelation.Error())).Return(nil) }, alertsToBeCreated: alertsToBeCreated, wantErr: true, }, { name: "should handle errors from repository", - setup: func(ar *mocks.Repository, at *mocks.AlertTransformer) { + setup: func(ar *mocks.Repository, at *mocks.AlertTransformer, ns *mocks.NotificationService) { + ar.EXPECT().WithTransaction(mock.AnythingOfType("context.todoCtx")).Return(ctx) at.EXPECT().TransformToAlerts(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("uint64"), mock.AnythingOfType("uint64"), mock.AnythingOfType("map[string]interface {}")).Return([]alert.Alert{ {ID: 1, ProviderID: 1, ResourceName: "foo", Severity: "CRITICAL", MetricName: "lag", MetricValue: "20", Rule: "lagHigh", TriggeredAt: timenow}, }, 1, nil) ar.EXPECT().Create(mock.AnythingOfType("context.todoCtx"), mock.Anything).Return(alert.Alert{}, errors.New("random error")) + ar.EXPECT().Rollback(mock.AnythingOfType("context.todoCtx"), errors.New("random error")).Return(nil) + }, alertsToBeCreated: alertsToBeCreated, wantErr: true, @@ -167,18 +180,19 @@ func TestService_CreateAlerts(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { var ( - repositoryMock = &mocks.Repository{} - alertTransformerMock = &mocks.AlertTransformer{} + repositoryMock = &mocks.Repository{} + alertTransformerMock = &mocks.AlertTransformer{} + notificationServiceMock = &mocks.NotificationService{} ) if tc.setup != nil { - tc.setup(repositoryMock, alertTransformerMock) + tc.setup(repositoryMock, alertTransformerMock, notificationServiceMock) } - svc := alert.NewService(repositoryMock, nil, map[string]alert.AlertTransformer{ + svc := alert.NewService(alert.Config{}, saltlog.NewNoop(), repositoryMock, nil, notificationServiceMock, map[string]alert.AlertTransformer{ testType: alertTransformerMock, }) - actualAlerts, firingLen, err := svc.CreateAlerts(ctx, testType, 1, 1, tc.alertsToBeCreated) + actualAlerts, err := svc.CreateAlerts(ctx, testType, 1, 1, tc.alertsToBeCreated) if tc.wantErr { if err == nil { t.Error("error should not be nil") @@ -187,7 +201,119 @@ func TestService_CreateAlerts(t *testing.T) { if diff := cmp.Diff(actualAlerts, tc.expectedAlerts); diff != "" { t.Errorf("result not equal, diff are %+v", diff) } - assert.Equal(t, tc.expectedFiringLen, firingLen) + } + }) + } +} + +func TestService_BuildNotifications(t *testing.T) { + tests := []struct { + name string + alerts []alert.Alert + firingLen int + want []notification.Notification + errString string + }{ + + { + name: "should return empty notification if alerts slice is empty", + errString: "empty alerts", + }, + { + name: "should properly return notification (same annotations are joined by newline and different labels are splitted into two notifications)", + alerts: []alert.Alert{ + { + ID: 14, + ProviderID: 1, + NamespaceID: 1, + ResourceName: "test-alert-host-1", + MetricName: "test-alert", + MetricValue: "15", + Severity: "WARNING", + Rule: "test-alert-template", + Labels: map[string]string{"lk1": "lv1"}, + Annotations: map[string]string{"ak1": "akv1"}, + Status: "FIRING", + }, + { + ID: 15, + ProviderID: 1, + NamespaceID: 1, + ResourceName: "test-alert-host-2", + MetricName: "test-alert", + MetricValue: "16", + Severity: "WARNING", + Rule: "test-alert-template", + Labels: map[string]string{"lk1": "lv1", "lk2": "lv2"}, + Annotations: map[string]string{"ak1": "akv1"}, + Status: "FIRING", + }, + { + ID: 16, + ProviderID: 1, + NamespaceID: 1, + ResourceName: "test-alert-host-2", + MetricName: "test-alert", + MetricValue: "16", + Severity: "WARNING", + Rule: "test-alert-template", + Labels: map[string]string{"lk1": "lv1", "lk2": "lv2"}, + Annotations: map[string]string{"ak1": "akv11", "ak2": "akv2"}, + Status: "FIRING", + }, + }, + firingLen: 2, + want: []notification.Notification{ + { + NamespaceID: 1, + Type: notification.TypeAlert, + Data: map[string]any{ + "generator_url": "", + "num_alerts_firing": 2, + "status": "FIRING", + "ak1": "akv1", + "lk1": "lv1", + }, + Labels: map[string]string{ + "lk1": "lv1", + }, + UniqueKey: "ignored", + Template: template.ReservedName_SystemDefault, + AlertIDs: []int64{14}, + }, + { + NamespaceID: 1, + Type: notification.TypeAlert, + + Data: map[string]any{ + "generator_url": "", + "num_alerts_firing": 2, + "status": "FIRING", + "ak1": "akv1\nakv11", + "ak2": "akv2", + "lk1": "lv1", + "lk2": "lv2", + }, + Labels: map[string]string{ + "lk1": "lv1", + "lk2": "lv2", + }, + UniqueKey: "ignored", + Template: template.ReservedName_SystemDefault, + AlertIDs: []int64{15, 16}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := alert.BuildNotifications(tt.alerts, tt.firingLen, time.Time{}, []string{}) + if (err != nil) && (err.Error() != tt.errString) { + t.Errorf("BuildNotifications() error = %v, wantErr %s", err, tt.errString) + return + } + if diff := cmp.Diff(got, tt.want, cmpopts.IgnoreFields(notification.Notification{}, "ID", "UniqueKey"), cmpopts.SortSlices(func(a, b notification.Notification) bool { return len(a.Labels) < len(b.Labels) })); diff != "" { + t.Errorf("BuildNotifications() got diff = %v", diff) } }) } diff --git a/core/alert/silence.go b/core/alert/silence.go index f7329e89..4405f3ee 100644 --- a/core/alert/silence.go +++ b/core/alert/silence.go @@ -1,15 +1 @@ package alert - -const ( - SilenceStatusTotal = "total" - SilenceStatusPartial = "partial" -) - -func silenceStatus(hasSilenced, hasNonSilenced bool) string { - if hasSilenced && !hasNonSilenced { - return SilenceStatusTotal - } else if hasSilenced && hasNonSilenced { - return SilenceStatusPartial - } - return "" -} diff --git a/core/notification/config.go b/core/notification/config.go index becd3744..212061ab 100644 --- a/core/notification/config.go +++ b/core/notification/config.go @@ -7,15 +7,16 @@ import ( ) type Config struct { - GroupBy []string `mapstructure:"group_by" yaml:"group_by"` MaxNumReceiverSelectors int `mapstructure:"max_num_receiver_selectors" yaml:"max_num_receiver_selectors" default:"10"` MaxMessagesReceiverFlow int `mapstructure:"max_messages_receiver_flow" yaml:"max_messages_receiver_flow" default:"10"` Queue queues.Config `mapstructure:"queue" yaml:"queue"` MessageHandler HandlerConfig `mapstructure:"message_handler" yaml:"message_handler"` DLQHandler HandlerConfig `mapstructure:"dlq_handler" yaml:"dlq_handler"` + GroupBy []string `mapstructure:"group_by" yaml:"group_by"` // experimental: derived from service.Config SubscriptionV2Enabled bool + EnableSilenceFeature bool } type HandlerConfig struct { diff --git a/core/notification/dispatch_bulk_notification_service.go b/core/notification/dispatch_bulk_notification_service.go new file mode 100644 index 00000000..1a051689 --- /dev/null +++ b/core/notification/dispatch_bulk_notification_service.go @@ -0,0 +1,186 @@ +package notification + +import ( + "context" + "fmt" + + "github.com/goto/siren/core/log" + "github.com/goto/siren/pkg/errors" + "github.com/goto/siren/pkg/structure" + "github.com/mitchellh/hashstructure/v2" + "golang.org/x/exp/maps" +) + +// DispatchBulkNotificationService only supports subscriber routing and not supporting direct receiver routing +type DispatchBulkNotificationService struct { + deps Deps + notifierPlugins map[string]Notifier + routerMap map[string]Router +} + +func NewDispatchBulkNotificationService( + deps Deps, + notifierPlugins map[string]Notifier, + routerMap map[string]Router, +) *DispatchBulkNotificationService { + return &DispatchBulkNotificationService{ + deps: deps, + notifierPlugins: notifierPlugins, + routerMap: routerMap, + } +} + +func (s *DispatchBulkNotificationService) getRouter(notificationRouterKind string) (Router, error) { + selectedRouter, exist := s.routerMap[notificationRouterKind] + if !exist { + return nil, errors.ErrInvalid.WithMsgf("unsupported notification router kind: %q", notificationRouterKind) + } + return selectedRouter, nil +} + +func (s *DispatchBulkNotificationService) prepareMetaMessages(ctx context.Context, ns []Notification) ([]MetaMessage, []log.Notification, error) { + var ( + metaMessages []MetaMessage + notificationLogs []log.Notification + ) + for _, n := range ns { + if err := n.Validate(RouterSubscriber); err != nil { + return nil, nil, err + } + + router, err := s.getRouter(RouterSubscriber) + if err != nil { + return nil, nil, err + } + + generatedMetaMessages, generatedNotificationLogs, err := router.PrepareMetaMessages(ctx, n) + if err != nil { + return nil, nil, err + } + + metaMessages = append(metaMessages, generatedMetaMessages...) + notificationLogs = append(notificationLogs, generatedNotificationLogs...) + } + + return metaMessages, notificationLogs, nil +} + +func (s *DispatchBulkNotificationService) Dispatch(ctx context.Context, ns []Notification) ([]string, error) { + var ( + notificationIDs []string + metaMessages []MetaMessage + notificationLogs []log.Notification + ) + + notifications, err := s.deps.Repository.BulkCreate(ctx, ns) + if err != nil { + return nil, err + } + + metaMessages, notificationLogs, err = s.prepareMetaMessages(ctx, notifications) + if err != nil { + return nil, err + } + + if err := s.deps.LogService.LogNotifications(ctx, notificationLogs...); err != nil { + return nil, fmt.Errorf("failed logging notifications: %w", err) + } + + reducedMetaMessages, err := ReduceMetaMessages(metaMessages, s.deps.Cfg.GroupBy) + if err != nil { + return nil, err + } + + messages, err := s.RenderMessages(ctx, reducedMetaMessages) + if err != nil { + return nil, err + } + + if len(messages) == 0 { + s.deps.Logger.Info("no messages to enqueue") + return nil, nil + } + + if err := s.deps.Q.Enqueue(ctx, messages...); err != nil { + return nil, fmt.Errorf("failed enqueuing messages: %w", err) + } + + for _, n := range notifications { + notificationIDs = append(notificationIDs, n.ID) + } + + return notificationIDs, nil +} + +func (s *DispatchBulkNotificationService) getNotifierPlugin(receiverType string) (Notifier, error) { + notifierPlugin, exist := s.notifierPlugins[receiverType] + if !exist { + return nil, errors.ErrInvalid.WithMsgf("unsupported receiver type: %q", receiverType) + } + return notifierPlugin, nil +} + +func (s *DispatchBulkNotificationService) RenderMessages(ctx context.Context, metaMessages []MetaMessage) (messages []Message, err error) { + for _, mm := range metaMessages { + notifierPlugin, err := s.getNotifierPlugin(mm.ReceiverType) + if err != nil { + return nil, err + } + + message, err := InitMessageByMetaMessage( + ctx, + notifierPlugin, + s.deps.TemplateService, + mm, + InitWithExpiryDuration(mm.ValidDuration), + ) + if err != nil { + return nil, err + } + + messages = append(messages, message) + } + return messages, nil +} + +func ReduceMetaMessages(metaMessages []MetaMessage, groupBy []string) ([]MetaMessage, error) { + var ( + hashedMetaMessagesMap = map[uint64]MetaMessage{} + ) + for _, mm := range metaMessages { + + groupedLabels := structure.BuildGroupLabels(mm.Labels, groupBy) + groupedLabels["_receiver.ID"] = fmt.Sprintf("%d", mm.ReceiverID) + groupedLabels["_notification.template"] = mm.Template + + hash, err := hashstructure.Hash(groupedLabels, hashstructure.FormatV2, nil) + if err != nil { + return nil, fmt.Errorf("cannot get hash from metamessage %v", mm) + } + + if _, ok := hashedMetaMessagesMap[hash]; !ok { + if mm.MergedLabels == nil { + mm.MergedLabels = map[string][]string{} + for k, v := range mm.Labels { + mm.MergedLabels[k] = append(mm.MergedLabels[k], v) + } + } + hashedMetaMessagesMap[hash] = mm + + } else { + hashedMetaMessagesMap[hash] = MergeMetaMessage(mm, hashedMetaMessagesMap[hash]) + } + + } + return maps.Values(hashedMetaMessagesMap), nil +} + +func MergeMetaMessage(from MetaMessage, to MetaMessage) MetaMessage { + var output = to + for k, v := range from.Labels { + output.MergedLabels[k] = append(output.MergedLabels[k], v) + } + output.NotificationIDs = append(output.NotificationIDs, from.NotificationIDs...) + output.SubscriptionIDs = append(output.SubscriptionIDs, from.SubscriptionIDs...) + return output +} diff --git a/core/notification/dispatch_bulk_notification_service_test.go b/core/notification/dispatch_bulk_notification_service_test.go new file mode 100644 index 00000000..8566424d --- /dev/null +++ b/core/notification/dispatch_bulk_notification_service_test.go @@ -0,0 +1,452 @@ +package notification_test + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + saltlog "github.com/goto/salt/log" + "github.com/goto/siren/core/log" + "github.com/goto/siren/core/notification" + "github.com/goto/siren/core/notification/mocks" + "github.com/goto/siren/core/template" + "github.com/goto/siren/pkg/errors" + "github.com/stretchr/testify/mock" +) + +func TestDispatchBulkNotificationServiceService_Dispatch(t *testing.T) { + tests := []struct { + name string + n []notification.Notification + setup func(*mocks.Repository, *mocks.AlertRepository, *mocks.Router, *mocks.LogService, *mocks.Queuer, *mocks.TemplateService, *mocks.Notifier) + wantErrString string + }{ + { + name: "should return error if repository return error", + n: []notification.Notification{ + { + Type: notification.TypeEvent, + Labels: map[string]string{ + "k1": "v1", + }, + }, + }, + setup: func(r *mocks.Repository, ar *mocks.AlertRepository, mr *mocks.Router, ls *mocks.LogService, q *mocks.Queuer, ts *mocks.TemplateService, nt *mocks.Notifier) { + r.EXPECT().BulkCreate(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("[]notification.Notification")).Return(nil, errors.New("some error")) + }, + wantErrString: "some error", + }, + { + name: "should return error if notification is not valid subscriber router", + n: []notification.Notification{}, + setup: func(r *mocks.Repository, ar *mocks.AlertRepository, mr *mocks.Router, ls *mocks.LogService, q *mocks.Queuer, ts *mocks.TemplateService, nt *mocks.Notifier) { + r.EXPECT().BulkCreate(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("[]notification.Notification")).Return([]notification.Notification{ + { + Type: notification.TypeEvent, + Labels: map[string]string{}, + }, + }, nil) + }, + wantErrString: "notification type subscriber should have labels: { 0 event map[] map[] 0s [] 0001-01-01 00:00:00 +0000 utc []}", + }, + { + name: "should return error if router prepare meta messages return error", + n: []notification.Notification{}, + setup: func(r *mocks.Repository, ar *mocks.AlertRepository, mr *mocks.Router, ls *mocks.LogService, q *mocks.Queuer, ts *mocks.TemplateService, nt *mocks.Notifier) { + r.EXPECT().BulkCreate(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("[]notification.Notification")).Return([]notification.Notification{ + { + Type: notification.TypeEvent, + Labels: map[string]string{ + "k1": "v1", + }, + }, + }, nil) + mr.EXPECT().PrepareMetaMessages(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(nil, nil, errors.New("some error")) + }, + wantErrString: "some error", + }, + { + name: "should return error if log notifications return error", + n: []notification.Notification{}, + setup: func(r *mocks.Repository, ar *mocks.AlertRepository, mr *mocks.Router, ls *mocks.LogService, q *mocks.Queuer, ts *mocks.TemplateService, nt *mocks.Notifier) { + r.EXPECT().BulkCreate(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("[]notification.Notification")).Return([]notification.Notification{ + { + Type: notification.TypeEvent, + Labels: map[string]string{ + "k1": "v1", + }, + }, + }, nil) + mr.EXPECT().PrepareMetaMessages(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(nil, []log.Notification{ + { + NamespaceID: 3, + SubscriptionID: 123, + ReceiverID: 12, + }, + }, nil) + ls.EXPECT().LogNotifications(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("log.Notification")).Return(errors.New("some error 3")) + + }, + wantErrString: "failed logging notifications: some error 3", + }, + { + name: "should return no error if successfully enqueue messages", + n: []notification.Notification{}, + setup: func(r *mocks.Repository, ar *mocks.AlertRepository, mr *mocks.Router, ls *mocks.LogService, q *mocks.Queuer, ts *mocks.TemplateService, nt *mocks.Notifier) { + r.EXPECT().BulkCreate(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("[]notification.Notification")).Return([]notification.Notification{ + { + Type: notification.TypeEvent, + Labels: map[string]string{ + "k1": "v1", + }, + }, + }, nil) + mr.EXPECT().PrepareMetaMessages(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(nil, []log.Notification{ + { + NamespaceID: 3, + SubscriptionID: 123, + ReceiverID: 12, + }, + }, nil) + ls.EXPECT().LogNotifications(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("log.Notification")).Return(nil) + nt.EXPECT().PreHookQueueTransformConfigs(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("map[string]any")).Return(map[string]any{}, nil) + nt.EXPECT().GetSystemDefaultTemplate().Return("system-template") + ts.EXPECT().GetByName(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("string")).Return(&template.Template{}, nil) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var ( + mockQueuer = new(mocks.Queuer) + mockRepository = new(mocks.Repository) + mockNotifier = new(mocks.Notifier) + mockTemplateService = new(mocks.TemplateService) + mockAlertRepository = new(mocks.AlertRepository) + mockLogService = new(mocks.LogService) + mockRouter = new(mocks.Router) + ) + + if tt.setup != nil { + tt.setup(mockRepository, mockAlertRepository, mockRouter, mockLogService, mockQueuer, mockTemplateService, mockNotifier) + } + + s := notification.NewDispatchBulkNotificationService( + notification.Deps{ + Cfg: notification.Config{ + SubscriptionV2Enabled: true, + EnableSilenceFeature: true, + }, + Logger: saltlog.NewNoop(), + Repository: mockRepository, + Q: mockQueuer, + AlertRepository: mockAlertRepository, + LogService: mockLogService, + }, + map[string]notification.Notifier{ + testType: mockNotifier, + }, + map[string]notification.Router{ + testType: mockRouter, + notification.RouterSubscriber: mockRouter, + }, + ) + if _, err := s.Dispatch(context.TODO(), tt.n); err != nil { + if err.Error() != tt.wantErrString { + t.Fatalf("error = %v, wantErr %s", err, tt.wantErrString) + } + } + }) + } +} + +func TestReduceMetaMessages(t *testing.T) { + tests := []struct { + name string + metaMessages []notification.MetaMessage + groupBy []string + want []notification.MetaMessage + wantErr bool + }{ + { + name: "should group meta messages with default receiver id", + metaMessages: []notification.MetaMessage{ + { + ReceiverID: 13, + NotificationIDs: []string{"xx"}, + SubscriptionIDs: []uint64{1, 2}, + Data: map[string]any{ + "d1": "dv1", + }, + Labels: map[string]string{ + "k1": "receiver-13", + "k2": "ab", + }, + }, + { + ReceiverID: 13, + NotificationIDs: []string{"yy"}, + SubscriptionIDs: []uint64{3, 4}, + Data: map[string]any{ + "d2": "dv2", + }, + Labels: map[string]string{ + "k1": "receiver-13", + "k2": "cd", + "x1": "x1", + }, + }, + { + ReceiverID: 14, + NotificationIDs: []string{"zz"}, + SubscriptionIDs: []uint64{3, 4}, + Data: map[string]any{ + "d3": "dv3", + }, + Labels: map[string]string{ + "k1": "receiver-14", + "k2": "ab", + }, + }, + }, + want: []notification.MetaMessage{ + { + ReceiverID: 13, + NotificationIDs: []string{"xx", "yy"}, + SubscriptionIDs: []uint64{1, 2, 3, 4}, + Data: map[string]any{ + "d1": "dv1", + }, + Labels: map[string]string{ + "k1": "receiver-13", + "k2": "ab", + }, + MergedLabels: map[string][]string{ + "k1": []string{"receiver-13", "receiver-13"}, + "k2": []string{"ab", "cd"}, + "x1": []string{"x1"}, + }, + }, + { + ReceiverID: 14, + NotificationIDs: []string{"zz"}, + SubscriptionIDs: []uint64{3, 4}, + Data: map[string]any{ + "d3": "dv3", + }, + Labels: map[string]string{ + "k1": "receiver-14", + "k2": "ab", + }, + MergedLabels: map[string][]string{ + "k1": {"receiver-14"}, + "k2": {"ab"}, + }, + }, + }, + }, + { + name: "should group meta messages with template", + metaMessages: []notification.MetaMessage{ + { + ReceiverID: 13, + NotificationIDs: []string{"xx"}, + SubscriptionIDs: []uint64{1, 2}, + Data: map[string]any{ + "d1": "dv1", + }, + Labels: map[string]string{ + "k1": "receiver-13", + "k2": "ab", + }, + Template: "template-1", + }, + { + ReceiverID: 13, + NotificationIDs: []string{"yy"}, + SubscriptionIDs: []uint64{3, 4}, + Data: map[string]any{ + "d2": "dv2", + }, + Labels: map[string]string{ + "k1": "receiver-13", + "k2": "cd", + "x1": "x1", + }, + Template: "template-2", + }, + { + ReceiverID: 14, + NotificationIDs: []string{"zz"}, + SubscriptionIDs: []uint64{3, 4}, + Data: map[string]any{ + "d3": "dv3", + }, + Labels: map[string]string{ + "k1": "receiver-14", + "k2": "ab", + }, + Template: "template-1", + }, + }, + want: []notification.MetaMessage{ + { + ReceiverID: 13, + NotificationIDs: []string{"xx"}, + SubscriptionIDs: []uint64{1, 2}, + Data: map[string]any{ + "d1": "dv1", + }, + Labels: map[string]string{ + "k1": "receiver-13", + "k2": "ab", + }, + Template: "template-1", + MergedLabels: map[string][]string{"k1": {"receiver-13"}, "k2": {"ab"}}, + }, + { + ReceiverID: 13, + NotificationIDs: []string{"yy"}, + SubscriptionIDs: []uint64{3, 4}, + Data: map[string]any{ + "d2": "dv2", + }, + Labels: map[string]string{ + "k1": "receiver-13", + "k2": "cd", + "x1": "x1", + }, + Template: "template-2", + MergedLabels: map[string][]string{"k1": {"receiver-13"}, "k2": {"cd"}, "x1": {"x1"}}, + }, + { + ReceiverID: 14, + NotificationIDs: []string{"zz"}, + SubscriptionIDs: []uint64{3, 4}, + Data: map[string]any{ + "d3": "dv3", + }, + Labels: map[string]string{ + "k1": "receiver-14", + "k2": "ab", + }, + Template: "template-1", + MergedLabels: map[string][]string{"k1": {"receiver-14"}, "k2": {"ab"}}, + }, + }, + }, + { + name: "should group meta messages with group by labels", + groupBy: []string{ + "k1", + "k2", + }, + metaMessages: []notification.MetaMessage{ + { + ReceiverID: 13, + NotificationIDs: []string{"xx"}, + SubscriptionIDs: []uint64{1, 2}, + Data: map[string]any{ + "d1": "dv1", + }, + Labels: map[string]string{ + "k1": "receiver-13", + "k2": "ab", + }, + }, + { + ReceiverID: 13, + NotificationIDs: []string{"yy"}, + SubscriptionIDs: []uint64{3, 4}, + Data: map[string]any{ + "d2": "dv2", + }, + Labels: map[string]string{ + "k1": "receiver-13", + "k2": "cd", + "x1": "x1", + }, + }, + { + ReceiverID: 13, + NotificationIDs: []string{"zz"}, + SubscriptionIDs: []uint64{3, 4}, + Data: map[string]any{ + "d3": "dv3", + }, + Labels: map[string]string{ + "k1": "receiver-14", + "k2": "ab", + }, + }, + { + ReceiverID: 13, + NotificationIDs: []string{"aa"}, + SubscriptionIDs: []uint64{5, 6}, + Data: map[string]any{ + "d3": "dv3", + }, + Labels: map[string]string{ + "k1": "receiver-13", + "k2": "ab", + }, + }, + }, + want: []notification.MetaMessage{ + { + ReceiverID: 13, + NotificationIDs: []string{"xx", "aa"}, + SubscriptionIDs: []uint64{1, 2, 5, 6}, + Data: map[string]any{ + "d1": "dv1", + }, + Labels: map[string]string{ + "k1": "receiver-13", + "k2": "ab", + }, + MergedLabels: map[string][]string{"k1": {"receiver-13", "receiver-13"}, "k2": {"ab", "ab"}}, + }, + { + ReceiverID: 13, + NotificationIDs: []string{"yy"}, + SubscriptionIDs: []uint64{3, 4}, + Data: map[string]any{ + "d2": "dv2", + }, + Labels: map[string]string{ + "k1": "receiver-13", + "k2": "cd", + "x1": "x1", + }, + MergedLabels: map[string][]string{"k1": {"receiver-13"}, "k2": {"cd"}, "x1": {"x1"}}, + }, + { + ReceiverID: 13, + NotificationIDs: []string{"zz"}, + SubscriptionIDs: []uint64{3, 4}, + Data: map[string]any{ + "d3": "dv3", + }, + Labels: map[string]string{ + "k1": "receiver-14", + "k2": "ab", + }, + MergedLabels: map[string][]string{"k1": {"receiver-14"}, "k2": {"ab"}}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := notification.ReduceMetaMessages(tt.metaMessages, tt.groupBy) + if (err != nil) != tt.wantErr { + t.Errorf("ReduceMetaMessages() error = %v, wantErr %v", err, tt.wantErr) + return + } + if diff := cmp.Diff(got, tt.want, cmpopts.SortSlices(func(i, j notification.MetaMessage) bool { return i.ReceiverID < j.ReceiverID })); diff != "" { + t.Errorf("ReduceMetaMessages() diff = %v", diff) + } + }) + } +} diff --git a/core/notification/dispatch_receiver_service.go b/core/notification/dispatch_receiver_service.go deleted file mode 100644 index 1fefc887..00000000 --- a/core/notification/dispatch_receiver_service.go +++ /dev/null @@ -1,104 +0,0 @@ -package notification - -import ( - "context" - - "github.com/goto/siren/core/log" - "github.com/goto/siren/core/receiver" - "github.com/goto/siren/pkg/errors" -) - -type DispatchReceiverConfig struct { - MaxMessagesReceiverFlow int - MaxNumReceiverSelectors int -} - -type DispatchReceiverService struct { - conf DispatchReceiverConfig - receiverService ReceiverService - templateService TemplateService - notifierPlugins map[string]Notifier -} - -func NewDispatchReceiverService( - conf DispatchReceiverConfig, - receiverService ReceiverService, - templateService TemplateService, - notifierPlugins map[string]Notifier) *DispatchReceiverService { - return &DispatchReceiverService{ - conf: conf, - receiverService: receiverService, - notifierPlugins: notifierPlugins, - templateService: templateService, - } -} - -func (s *DispatchReceiverService) getNotifierPlugin(receiverType string) (Notifier, error) { - notifierPlugin, exist := s.notifierPlugins[receiverType] - if !exist { - return nil, errors.ErrInvalid.WithMsgf("unsupported receiver type: %q", receiverType) - } - return notifierPlugin, nil -} - -func (s *DispatchReceiverService) PrepareMessageV2(ctx context.Context, n Notification) ([]Message, []log.Notification, bool, error) { - return s.PrepareMessage(ctx, n) -} - -func (s *DispatchReceiverService) PrepareMessage(ctx context.Context, n Notification) ([]Message, []log.Notification, bool, error) { - - var notificationLogs []log.Notification - - if len(n.ReceiverSelectors) > s.conf.MaxNumReceiverSelectors { - return nil, nil, false, errors.ErrInvalid.WithMsgf("number of receiver selectors should be less than or equal threshold %d", s.conf.MaxNumReceiverSelectors) - } - - rcvs, err := s.receiverService.List(ctx, receiver.Filter{ - MultipleLabels: n.ReceiverSelectors, - Expanded: true, - }) - if err != nil { - return nil, nil, false, err - } - - if len(rcvs) == 0 { - return nil, nil, false, errors.ErrNotFound - } - - var messages []Message - - for _, rcv := range rcvs { - notifierPlugin, err := s.getNotifierPlugin(rcv.Type) - if err != nil { - return nil, nil, false, errors.ErrInvalid.WithMsgf("invalid receiver type: %s", err.Error()) - } - - message, err := InitMessage( - ctx, - notifierPlugin, - s.templateService, - n, - rcv.Type, - rcv.Configurations, - InitWithExpiryDuration(n.ValidDuration), - ) - if err != nil { - return nil, nil, false, err - } - - messages = append(messages, message) - notificationLogs = append(notificationLogs, log.Notification{ - NamespaceID: n.NamespaceID, - NotificationID: n.ID, - ReceiverID: rcv.ID, - AlertIDs: n.AlertIDs, - }) - } - - var messagesNum = len(messages) - if messagesNum > s.conf.MaxMessagesReceiverFlow { - return nil, nil, false, errors.ErrInvalid.WithMsgf("sending %d messages exceed max messages receiver flow threshold %d. this will spam and broadcast to %d channel. found %d receiver selectors passed, you might want to check your receiver selectors configuration", messagesNum, s.conf.MaxMessagesReceiverFlow, messagesNum, len(n.ReceiverSelectors)) - } - - return messages, notificationLogs, false, nil -} diff --git a/core/notification/dispatch_single_notification_service.go b/core/notification/dispatch_single_notification_service.go new file mode 100644 index 00000000..14148195 --- /dev/null +++ b/core/notification/dispatch_single_notification_service.go @@ -0,0 +1,151 @@ +package notification + +import ( + "context" + "fmt" + + "github.com/goto/siren/core/log" + "github.com/goto/siren/core/silence" + "github.com/goto/siren/pkg/errors" +) + +// DispatchSingleNotificationService supports subscriber routing and receiver routing at the same time +type DispatchSingleNotificationService struct { + deps Deps + notifierPlugins map[string]Notifier + routerMap map[string]Router +} + +func NewDispatchSingleNotificationService( + deps Deps, + notifierPlugins map[string]Notifier, + routerMap map[string]Router, +) *DispatchSingleNotificationService { + return &DispatchSingleNotificationService{ + deps: deps, + notifierPlugins: notifierPlugins, + routerMap: routerMap, + } +} + +func (s *DispatchSingleNotificationService) getRouter(notificationRouterKind string) (Router, error) { + selectedRouter, exist := s.routerMap[notificationRouterKind] + if !exist { + return nil, errors.ErrInvalid.WithMsgf("unsupported notification router kind: %q", notificationRouterKind) + } + return selectedRouter, nil +} + +func (s *DispatchSingleNotificationService) Dispatch(ctx context.Context, ns []Notification) ([]string, error) { + if len(ns) != 1 { + return nil, errors.ErrInvalid.WithMsgf("direct single notification should only accept 1 notification but found %d", len(ns)) + } + + var ( + n = ns[0] + messages []Message + ) + + no, err := s.deps.Repository.Create(ctx, n) + if err != nil { + return nil, err + } + + n.EnrichID(no.ID) + + switch n.Type { + case TypeAlert: + messages, err = s.dispatchByRouter(ctx, n, RouterSubscriber) + if err != nil { + return nil, err + } + case TypeEvent: + messages, err = s.fetchMessagesEvents(ctx, n) + if err != nil { + return nil, err + } + default: + return nil, errors.ErrInternal.WithMsgf("unknown notification type %s", n.Type) + } + + if len(messages) == 0 { + s.deps.Logger.Info("no messages to enqueue") + return []string{n.ID}, nil + } + + if err := s.deps.Q.Enqueue(ctx, messages...); err != nil { + return nil, fmt.Errorf("failed enqueuing messages: %w", err) + } + + return []string{n.ID}, nil +} + +func (s *DispatchSingleNotificationService) dispatchByRouter(ctx context.Context, n Notification, flow string) ([]Message, error) { + if err := n.Validate(flow); err != nil { + return nil, err + } + + router, err := s.getRouter(flow) + if err != nil { + return nil, err + } + + var ( + messages []Message + notificationLogs []log.Notification + hasSilenced bool + ) + if s.deps.Cfg.SubscriptionV2Enabled { + messages, notificationLogs, hasSilenced, err = router.PrepareMessageV2(ctx, n) + if err != nil { + return nil, err + } + } else { + messages, notificationLogs, hasSilenced, err = router.PrepareMessage(ctx, n) + if err != nil { + return nil, err + } + } + + if len(messages) == 0 && len(notificationLogs) == 0 { + return nil, fmt.Errorf("something wrong and no messages will be sent with notification: %v", n) + } + + if err := s.deps.LogService.LogNotifications(ctx, notificationLogs...); err != nil { + return nil, fmt.Errorf("failed logging notifications: %w", err) + } + + // Reliability of silence feature need to be tested more + if s.deps.Cfg.EnableSilenceFeature { + if err := s.deps.AlertRepository.BulkUpdateSilence(ctx, n.AlertIDs, silence.Status(hasSilenced, len(messages) != 0)); err != nil { + return nil, fmt.Errorf("failed updating silence status: %w", err) + } + } + + return messages, nil +} + +func (s *DispatchSingleNotificationService) fetchMessagesEvents(ctx context.Context, n Notification) ([]Message, error) { + if len(n.ReceiverSelectors) == 0 && len(n.Labels) == 0 { + return nil, errors.ErrInvalid.WithMsgf("no receivers found") + } + + var messages = []Message{} + + if len(n.ReceiverSelectors) != 0 { + generatedMessages, err := s.dispatchByRouter(ctx, n, RouterReceiver) + if err != nil { + return nil, err + } + messages = append(messages, generatedMessages...) + } + + if len(n.Labels) != 0 { + generatedMessages, err := s.dispatchByRouter(ctx, n, RouterSubscriber) + if err != nil { + return nil, err + } + messages = append(messages, generatedMessages...) + } + return messages, nil +} diff --git a/core/notification/dispatch_single_notification_service_test.go b/core/notification/dispatch_single_notification_service_test.go new file mode 100644 index 00000000..eb0d578e --- /dev/null +++ b/core/notification/dispatch_single_notification_service_test.go @@ -0,0 +1,374 @@ +package notification_test + +import ( + "context" + "errors" + "testing" + + saltlog "github.com/goto/salt/log" + "github.com/goto/siren/core/log" + "github.com/goto/siren/core/notification" + "github.com/goto/siren/core/notification/mocks" + "github.com/stretchr/testify/mock" +) + +func TestDispatchSingleNotificationServiceService_Dispatch(t *testing.T) { + tests := []struct { + name string + n []notification.Notification + setup func(*mocks.Repository, *mocks.AlertRepository, *mocks.Router, *mocks.LogService, *mocks.Queuer) + wantErrString string + }{ + { + name: "should return error if notifications arg is not 1", + n: []notification.Notification{}, + wantErrString: "direct single notification should only accept 1 notification but found 0", + }, + { + name: "should return error if repository return error", + n: []notification.Notification{ + { + Type: notification.TypeAlert, + Labels: map[string]string{ + "k1": "v1", + }, + }, + }, + setup: func(r *mocks.Repository, ar *mocks.AlertRepository, mr *mocks.Router, ls *mocks.LogService, q *mocks.Queuer) { + r.EXPECT().Create(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(notification.Notification{}, errors.New("some error")) + }, + wantErrString: "some error", + }, + { + name: "should return error if notification type is unknown", + n: []notification.Notification{ + { + Type: "random", + Labels: map[string]string{}, + }, + }, + setup: func(r *mocks.Repository, ar *mocks.AlertRepository, mr *mocks.Router, ls *mocks.LogService, q *mocks.Queuer) { + r.EXPECT().Create(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(notification.Notification{}, nil) + }, + wantErrString: "unknown notification type random", + }, + { + name: "should return error if log notifications return error", + n: []notification.Notification{ + { + Type: notification.TypeAlert, + Labels: map[string]string{ + "k1": "v1", + }, + }, + }, + setup: func(r *mocks.Repository, ar *mocks.AlertRepository, mr *mocks.Router, ls *mocks.LogService, q *mocks.Queuer) { + r.EXPECT().Create(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(notification.Notification{}, nil) + mr.EXPECT().PrepareMessageV2(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(nil, []log.Notification{ + { + NamespaceID: 3, + SubscriptionID: 123, + ReceiverID: 12, + }, + }, false, nil) + ls.EXPECT().LogNotifications(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("log.Notification")).Return(errors.New("some error 3")) + }, + wantErrString: "failed logging notifications: some error 3", + }, + { + name: "should return error if update alerts silence status return error", + n: []notification.Notification{ + { + Type: notification.TypeAlert, + Labels: map[string]string{ + "k1": "v1", + }, + }, + }, + setup: func(r *mocks.Repository, ar *mocks.AlertRepository, mr *mocks.Router, ls *mocks.LogService, q *mocks.Queuer) { + r.EXPECT().Create(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(notification.Notification{}, nil) + mr.EXPECT().PrepareMessageV2(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(nil, []log.Notification{ + { + NamespaceID: 3, + SubscriptionID: 123, + ReceiverID: 12, + }, + }, false, nil) + ls.EXPECT().LogNotifications(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("log.Notification")).Return(nil) + ar.EXPECT().BulkUpdateSilence(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("[]int64"), mock.AnythingOfType("string")).Return(errors.New("some error 4")) + }, + wantErrString: "failed updating silence status: some error 4", + }, + { + name: "should return no error if no messages to queue", + n: []notification.Notification{ + { + Type: notification.TypeAlert, + Labels: map[string]string{ + "k1": "v1", + }, + }, + }, + setup: func(r *mocks.Repository, ar *mocks.AlertRepository, mr *mocks.Router, ls *mocks.LogService, q *mocks.Queuer) { + r.EXPECT().Create(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(notification.Notification{}, nil) + mr.EXPECT().PrepareMessageV2(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(nil, []log.Notification{ + { + NamespaceID: 3, + SubscriptionID: 123, + ReceiverID: 12, + }, + }, false, nil) + ls.EXPECT().LogNotifications(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("log.Notification")).Return(nil) + ar.EXPECT().BulkUpdateSilence(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("[]int64"), mock.AnythingOfType("string")).Return(nil) + }, + }, + { + name: "should return error if queueing error", + n: []notification.Notification{ + { + Type: notification.TypeAlert, + Labels: map[string]string{ + "k1": "v1", + }, + }, + }, + setup: func(r *mocks.Repository, ar *mocks.AlertRepository, mr *mocks.Router, ls *mocks.LogService, q *mocks.Queuer) { + r.EXPECT().Create(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(notification.Notification{}, nil) + mr.EXPECT().PrepareMessageV2(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return([]notification.Message{ + { + ID: "1234", + NotificationIDs: []string{"n-1234"}, + }, + }, []log.Notification{ + { + NamespaceID: 3, + SubscriptionID: 123, + ReceiverID: 12, + }, + }, false, nil) + ls.EXPECT().LogNotifications(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("log.Notification")).Return(nil) + ar.EXPECT().BulkUpdateSilence(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("[]int64"), mock.AnythingOfType("string")).Return(nil) + q.EXPECT().Enqueue(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Message")).Return(errors.New("some error 5")) + }, + wantErrString: "failed enqueuing messages: some error 5", + }, + + { + name: "should return no error if queueing succeed", + n: []notification.Notification{ + { + Type: notification.TypeAlert, + Labels: map[string]string{ + "k1": "v1", + }, + }, + }, + setup: func(r *mocks.Repository, ar *mocks.AlertRepository, mr *mocks.Router, ls *mocks.LogService, q *mocks.Queuer) { + r.EXPECT().Create(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(notification.Notification{}, nil) + mr.EXPECT().PrepareMessageV2(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return([]notification.Message{ + { + ID: "1234", + NotificationIDs: []string{"n-1234"}, + }, + }, []log.Notification{ + { + NamespaceID: 3, + SubscriptionID: 123, + ReceiverID: 12, + }, + }, false, nil) + ls.EXPECT().LogNotifications(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("log.Notification")).Return(nil) + ar.EXPECT().BulkUpdateSilence(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("[]int64"), mock.AnythingOfType("string")).Return(nil) + q.EXPECT().Enqueue(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Message")).Return(nil) + }, + wantErrString: "failed enqueuing messages: some error 5", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var ( + mockQueuer = new(mocks.Queuer) + mockRepository = new(mocks.Repository) + mockAlertRepository = new(mocks.AlertRepository) + mockLogService = new(mocks.LogService) + mockNotifier = new(mocks.Notifier) + mockRouter = new(mocks.Router) + ) + + if tt.setup != nil { + tt.setup(mockRepository, mockAlertRepository, mockRouter, mockLogService, mockQueuer) + } + + s := notification.NewDispatchSingleNotificationService( + notification.Deps{ + Cfg: notification.Config{ + SubscriptionV2Enabled: true, + EnableSilenceFeature: true, + }, + Logger: saltlog.NewNoop(), + Repository: mockRepository, + Q: mockQueuer, + AlertRepository: mockAlertRepository, + LogService: mockLogService, + }, + map[string]notification.Notifier{ + testType: mockNotifier, + }, + map[string]notification.Router{ + testType: mockRouter, + notification.RouterSubscriber: mockRouter, + }, + ) + if _, err := s.Dispatch(context.TODO(), tt.n); err != nil { + if err.Error() != tt.wantErrString { + t.Fatalf("Service.DispatchFailure() error = %v, wantErr %s", err, tt.wantErrString) + } + } + }) + } +} + +func TestDispatchSingleNotificationServiceService_DispatchFailureAlert(t *testing.T) { + tests := []struct { + name string + n []notification.Notification + setup func(*mocks.Repository, *mocks.AlertRepository, *mocks.Router, *mocks.LogService, *mocks.Queuer) + wantErrString string + }{ + { + name: "should return error if dispatchAlert.Validate return error", + n: []notification.Notification{ + { + Type: notification.TypeAlert, + Labels: map[string]string{}, + }, + }, + setup: func(r *mocks.Repository, ar *mocks.AlertRepository, mr *mocks.Router, ls *mocks.LogService, q *mocks.Queuer) { + r.EXPECT().Create(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(notification.Notification{}, nil) + }, + wantErrString: "notification type subscriber should have labels: { 0 alert map[id:] map[] 0s [] 0001-01-01 00:00:00 +0000 utc []}", + }, + { + name: "should return error if dispatchAlert no messages generated but not return subscription not found", + n: []notification.Notification{ + { + Type: notification.TypeAlert, + Labels: map[string]string{ + "k1": "v1", + }, + }, + }, + setup: func(r *mocks.Repository, ar *mocks.AlertRepository, mr *mocks.Router, ls *mocks.LogService, q *mocks.Queuer) { + r.EXPECT().Create(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(notification.Notification{}, nil) + mr.EXPECT().PrepareMessageV2(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(nil, nil, false, nil) + }, + wantErrString: "something wrong and no messages will be sent with notification: { 0 alert map[id:] map[k1:v1] 0s [] 0001-01-01 00:00:00 +0000 UTC []}", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var ( + mockQueuer = new(mocks.Queuer) + mockRepository = new(mocks.Repository) + mockAlertRepository = new(mocks.AlertRepository) + mockLogService = new(mocks.LogService) + mockNotifier = new(mocks.Notifier) + mockRouter = new(mocks.Router) + ) + + if tt.setup != nil { + tt.setup(mockRepository, mockAlertRepository, mockRouter, mockLogService, mockQueuer) + } + + s := notification.NewDispatchSingleNotificationService( + notification.Deps{ + Cfg: notification.Config{ + SubscriptionV2Enabled: true, + EnableSilenceFeature: true, + }, + Logger: saltlog.NewNoop(), + Repository: mockRepository, + Q: mockQueuer, + AlertRepository: mockAlertRepository, + LogService: mockLogService, + }, + map[string]notification.Notifier{ + testType: mockNotifier, + }, + map[string]notification.Router{ + testType: mockRouter, + notification.RouterSubscriber: mockRouter, + }, + ) + if _, err := s.Dispatch(context.TODO(), tt.n); err != nil { + if err.Error() != tt.wantErrString { + t.Fatalf("Service.DispatchFailureAlert() error = %v, wantErr %s", err, tt.wantErrString) + } + } + }) + } +} + +func TestDispatchSingleNotificationServiceService_DispatchFailureEvent(t *testing.T) { + tests := []struct { + name string + n []notification.Notification + setup func(*mocks.Repository, *mocks.AlertRepository, *mocks.Router, *mocks.LogService, *mocks.Queuer) + wantErrString string + }{ + { + name: "should return error if dispatchEvent found no receiver", + n: []notification.Notification{ + { + Type: notification.TypeEvent, + }, + }, + setup: func(r *mocks.Repository, ar *mocks.AlertRepository, mr *mocks.Router, ls *mocks.LogService, q *mocks.Queuer) { + r.EXPECT().Create(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(notification.Notification{}, nil) + }, + wantErrString: "no receivers found", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var ( + mockQueuer = new(mocks.Queuer) + mockRepository = new(mocks.Repository) + mockAlertRepository = new(mocks.AlertRepository) + mockLogService = new(mocks.LogService) + mockNotifier = new(mocks.Notifier) + mockRouter = new(mocks.Router) + ) + + if tt.setup != nil { + tt.setup(mockRepository, mockAlertRepository, mockRouter, mockLogService, mockQueuer) + } + + s := notification.NewDispatchSingleNotificationService( + notification.Deps{ + Cfg: notification.Config{ + SubscriptionV2Enabled: true, + EnableSilenceFeature: true, + }, + Logger: saltlog.NewNoop(), + Repository: mockRepository, + Q: mockQueuer, + AlertRepository: mockAlertRepository, + LogService: mockLogService, + }, + map[string]notification.Notifier{ + testType: mockNotifier, + }, + map[string]notification.Router{ + testType: mockRouter, + notification.RouterSubscriber: mockRouter, + }, + ) + if _, err := s.Dispatch(context.TODO(), tt.n); err != nil { + if err.Error() != tt.wantErrString { + t.Fatalf("Service.DispatchFailureEvent() error = %v, wantErr %s", err, tt.wantErrString) + } + } + }) + } +} diff --git a/core/notification/errors.go b/core/notification/errors.go index d6bbb7ea..fac0776d 100644 --- a/core/notification/errors.go +++ b/core/notification/errors.go @@ -1,5 +1,7 @@ package notification -import "errors" +import ( + "errors" +) var ErrNoMessage = errors.New("no message found") diff --git a/core/notification/handler_test.go b/core/notification/handler_test.go index a7b132f2..69223c98 100644 --- a/core/notification/handler_test.go +++ b/core/notification/handler_test.go @@ -36,7 +36,7 @@ func TestHandler_MessageHandler(t *testing.T) { name: "return error if post hook transform config is failing and error callback success", messages: []notification.Message{ { - ReceiverType: testPluginType, + ReceiverType: testType, }, }, setup: func(q *mocks.Queuer, n *mocks.Notifier) { @@ -49,7 +49,7 @@ func TestHandler_MessageHandler(t *testing.T) { name: "return error if post hook transform config is failing and error callback is failing", messages: []notification.Message{ { - ReceiverType: testPluginType, + ReceiverType: testType, }, }, setup: func(q *mocks.Queuer, n *mocks.Notifier) { @@ -62,7 +62,7 @@ func TestHandler_MessageHandler(t *testing.T) { name: "return error if send message return error and error handler queue return error", messages: []notification.Message{ { - ReceiverType: testPluginType, + ReceiverType: testType, }, }, setup: func(q *mocks.Queuer, n *mocks.Notifier) { @@ -76,7 +76,7 @@ func TestHandler_MessageHandler(t *testing.T) { name: "return error if send message return error and error handler queue return no error", messages: []notification.Message{ { - ReceiverType: testPluginType, + ReceiverType: testType, }, }, setup: func(q *mocks.Queuer, n *mocks.Notifier) { @@ -90,7 +90,7 @@ func TestHandler_MessageHandler(t *testing.T) { name: "return error if send message success and success handler queue return error", messages: []notification.Message{ { - ReceiverType: testPluginType, + ReceiverType: testType, }, }, setup: func(q *mocks.Queuer, n *mocks.Notifier) { @@ -104,7 +104,7 @@ func TestHandler_MessageHandler(t *testing.T) { name: "return no error if send message success and success handler queue return no error", messages: []notification.Message{ { - ReceiverType: testPluginType, + ReceiverType: testType, }, }, setup: func(q *mocks.Queuer, n *mocks.Notifier) { diff --git a/core/notification/message.go b/core/notification/message.go index 4dee4c8e..59c7d079 100644 --- a/core/notification/message.go +++ b/core/notification/message.go @@ -63,16 +63,16 @@ func InitWithMaxTries(mt int) MessageOption { // Message is the model to be sent for a specific subscription's receiver type Message struct { - ID string - NotificationID string - Status MessageStatus - ReceiverType string - Configs map[string]any // the datasource to build vendor-specific configs - Details map[string]any // the datasource to build vendor-specific message - MaxTries int - ExpiredAt time.Time - CreatedAt time.Time - UpdatedAt time.Time + ID string + NotificationIDs []string + Status MessageStatus + ReceiverType string + Configs map[string]any // the datasource to build vendor-specific configs + Details map[string]any // the datasource to build vendor-specific message + MaxTries int + ExpiredAt time.Time + CreatedAt time.Time + UpdatedAt time.Time LastError string TryCount int @@ -98,13 +98,13 @@ func InitMessage( ) m := &Message{ - ID: uuid.NewString(), - NotificationID: n.ID, - Status: MessageStatusEnqueued, - ReceiverType: receiverType, - MaxTries: defaultMaxTries, - CreatedAt: timeNow, - UpdatedAt: timeNow, + ID: uuid.NewString(), + NotificationIDs: []string{n.ID}, + Status: MessageStatusEnqueued, + ReceiverType: receiverType, + MaxTries: defaultMaxTries, + CreatedAt: timeNow, + UpdatedAt: timeNow, } if notifierPlugin == nil { @@ -180,6 +180,101 @@ func InitMessage( return *m, nil } +func InitMessageByMetaMessage( + ctx context.Context, + notifierPlugin Notifier, + templateService TemplateService, + mm MetaMessage, + opts ...MessageOption, +) (Message, error) { + var ( + timeNow = time.Now() + details = make(map[string]any) + ) + + m := &Message{ + ID: uuid.NewString(), + NotificationIDs: mm.NotificationIDs, + Status: MessageStatusEnqueued, + ReceiverType: mm.ReceiverType, + MaxTries: defaultMaxTries, + CreatedAt: timeNow, + UpdatedAt: timeNow, + } + + if notifierPlugin == nil { + return Message{}, errors.New("notifierPlugin cannot be nil") + } + + newConfigs, err := notifierPlugin.PreHookQueueTransformConfigs(ctx, mm.ReceiverConfigs) + if err != nil { + return Message{}, err + } + m.Configs = newConfigs + + for _, opt := range opts { + opt(m) + } + + if m.expiryDuration != 0 { + m.ExpiredAt = m.CreatedAt.Add(m.expiryDuration) + } + + if mm.Template == "" { + return Message{}, errors.ErrInvalid.WithMsgf("found no template, template is mandatory") + } + //TODO fetch template if any, if not exist, check provider type, if exist use the default template, if not pass as-is + // if there is template, render and replace detail with the new one + + var contentTemplate string + + if template.IsReservedName(mm.Template) { + contentTemplate = notifierPlugin.GetSystemDefaultTemplate() + } else { + tmpl, err := templateService.GetByName(ctx, mm.Template) + if err != nil { + return Message{}, err + } + + templateMessages, err := template.MessagesFromBody(tmpl) + if err != nil { + return Message{}, err + } + + contentTmpl, err := template.MessageContentByReceiverType(templateMessages, mm.ReceiverType) + if err != nil { + return Message{}, err + } + contentTemplate = contentTmpl + } + + if contentTemplate != "" { + renderedDetailString, err := template.RenderBody(contentTemplate, mm, template.DelimMessageLeft, template.DelimMessageRight) + if err != nil { + return Message{}, errors.ErrInvalid.WithMsgf("failed to render template receiver %s: %s", mm.ReceiverType, err.Error()) + } + + var messageDetails map[string]any + if err := yaml.Unmarshal([]byte(renderedDetailString), &messageDetails); err != nil { + return Message{}, errors.ErrInvalid.WithMsgf("failed to unmarshal rendered template receiver %s: %s, rendered template: %v", mm.ReceiverType, err.Error(), renderedDetailString) + } + m.Details = messageDetails + } else { + for k, v := range mm.Labels { + details[k] = v + } + for k, v := range mm.Data { + details[k] = v + } + + m.Details = details + } + + m.Details[DetailsKeyNotificationType] = mm.NotificationType + + return *m, nil +} + // MarkFailed update message to the failed state func (m *Message) MarkFailed(updatedAt time.Time, retryable bool, err error) { m.TryCount = m.TryCount + 1 diff --git a/core/notification/message_test.go b/core/notification/message_test.go index 1d21bb8d..7e818135 100644 --- a/core/notification/message_test.go +++ b/core/notification/message_test.go @@ -36,7 +36,8 @@ func TestMessage_InitMessage(t *testing.T) { n.EXPECT().GetSystemDefaultTemplate().Return("") }, n: notification.Notification{ - Type: notification.FlowSubscriber, + ID: "aa", + Type: notification.RouterSubscriber, Labels: map[string]string{ "labelkey1": "value1", "samekey": "label_value", @@ -48,13 +49,14 @@ func TestMessage_InitMessage(t *testing.T) { Template: template.ReservedName_SystemDefault, }, want: notification.Message{ - ID: testID, - Status: notification.MessageStatusEnqueued, + ID: testID, + NotificationIDs: []string{"aa"}, + Status: notification.MessageStatusEnqueued, Details: map[string]any{ "labelkey1": "value1", "varkey1": "value1", "samekey": "var_value", - notification.DetailsKeyNotificationType: notification.FlowSubscriber, + notification.DetailsKeyNotificationType: notification.RouterSubscriber, }, CreatedAt: testTimeNow, UpdatedAt: testTimeNow, diff --git a/core/notification/metamessage.go b/core/notification/metamessage.go new file mode 100644 index 00000000..a37617cf --- /dev/null +++ b/core/notification/metamessage.go @@ -0,0 +1,19 @@ +package notification + +import ( + "time" +) + +type MetaMessage struct { + ReceiverID uint64 + SubscriptionIDs []uint64 + ReceiverType string + NotificationIDs []string + NotificationType string + ReceiverConfigs map[string]any + Data map[string]any + ValidDuration time.Duration + Template string + Labels map[string]string + MergedLabels map[string][]string +} diff --git a/core/notification/mocks/alert_repository.go b/core/notification/mocks/alert_repository.go new file mode 100644 index 00000000..aae988aa --- /dev/null +++ b/core/notification/mocks/alert_repository.go @@ -0,0 +1,84 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// AlertRepository is an autogenerated mock type for the AlertRepository type +type AlertRepository struct { + mock.Mock +} + +type AlertRepository_Expecter struct { + mock *mock.Mock +} + +func (_m *AlertRepository) EXPECT() *AlertRepository_Expecter { + return &AlertRepository_Expecter{mock: &_m.Mock} +} + +// BulkUpdateSilence provides a mock function with given fields: _a0, _a1, _a2 +func (_m *AlertRepository) BulkUpdateSilence(_a0 context.Context, _a1 []int64, _a2 string) error { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for BulkUpdateSilence") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []int64, string) error); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// AlertRepository_BulkUpdateSilence_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BulkUpdateSilence' +type AlertRepository_BulkUpdateSilence_Call struct { + *mock.Call +} + +// BulkUpdateSilence is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 []int64 +// - _a2 string +func (_e *AlertRepository_Expecter) BulkUpdateSilence(_a0 interface{}, _a1 interface{}, _a2 interface{}) *AlertRepository_BulkUpdateSilence_Call { + return &AlertRepository_BulkUpdateSilence_Call{Call: _e.mock.On("BulkUpdateSilence", _a0, _a1, _a2)} +} + +func (_c *AlertRepository_BulkUpdateSilence_Call) Run(run func(_a0 context.Context, _a1 []int64, _a2 string)) *AlertRepository_BulkUpdateSilence_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].([]int64), args[2].(string)) + }) + return _c +} + +func (_c *AlertRepository_BulkUpdateSilence_Call) Return(_a0 error) *AlertRepository_BulkUpdateSilence_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *AlertRepository_BulkUpdateSilence_Call) RunAndReturn(run func(context.Context, []int64, string) error) *AlertRepository_BulkUpdateSilence_Call { + _c.Call.Return(run) + return _c +} + +// NewAlertRepository creates a new instance of AlertRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewAlertRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *AlertRepository { + mock := &AlertRepository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/notification/mocks/dispatcher.go b/core/notification/mocks/dispatcher.go index 7e4b6f0c..3c5a36c0 100644 --- a/core/notification/mocks/dispatcher.go +++ b/core/notification/mocks/dispatcher.go @@ -5,10 +5,8 @@ package mocks import ( context "context" - log "github.com/goto/siren/core/log" - mock "github.com/stretchr/testify/mock" - notification "github.com/goto/siren/core/notification" + mock "github.com/stretchr/testify/mock" ) // Dispatcher is an autogenerated mock type for the Dispatcher type @@ -24,152 +22,61 @@ func (_m *Dispatcher) EXPECT() *Dispatcher_Expecter { return &Dispatcher_Expecter{mock: &_m.Mock} } -// PrepareMessage provides a mock function with given fields: ctx, n -func (_m *Dispatcher) PrepareMessage(ctx context.Context, n notification.Notification) ([]notification.Message, []log.Notification, bool, error) { - ret := _m.Called(ctx, n) - - if len(ret) == 0 { - panic("no return value specified for PrepareMessage") - } - - var r0 []notification.Message - var r1 []log.Notification - var r2 bool - var r3 error - if rf, ok := ret.Get(0).(func(context.Context, notification.Notification) ([]notification.Message, []log.Notification, bool, error)); ok { - return rf(ctx, n) - } - if rf, ok := ret.Get(0).(func(context.Context, notification.Notification) []notification.Message); ok { - r0 = rf(ctx, n) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]notification.Message) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, notification.Notification) []log.Notification); ok { - r1 = rf(ctx, n) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).([]log.Notification) - } - } - - if rf, ok := ret.Get(2).(func(context.Context, notification.Notification) bool); ok { - r2 = rf(ctx, n) - } else { - r2 = ret.Get(2).(bool) - } - - if rf, ok := ret.Get(3).(func(context.Context, notification.Notification) error); ok { - r3 = rf(ctx, n) - } else { - r3 = ret.Error(3) - } - - return r0, r1, r2, r3 -} - -// Dispatcher_PrepareMessage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrepareMessage' -type Dispatcher_PrepareMessage_Call struct { - *mock.Call -} - -// PrepareMessage is a helper method to define mock.On call -// - ctx context.Context -// - n notification.Notification -func (_e *Dispatcher_Expecter) PrepareMessage(ctx interface{}, n interface{}) *Dispatcher_PrepareMessage_Call { - return &Dispatcher_PrepareMessage_Call{Call: _e.mock.On("PrepareMessage", ctx, n)} -} - -func (_c *Dispatcher_PrepareMessage_Call) Run(run func(ctx context.Context, n notification.Notification)) *Dispatcher_PrepareMessage_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(notification.Notification)) - }) - return _c -} - -func (_c *Dispatcher_PrepareMessage_Call) Return(_a0 []notification.Message, _a1 []log.Notification, _a2 bool, _a3 error) *Dispatcher_PrepareMessage_Call { - _c.Call.Return(_a0, _a1, _a2, _a3) - return _c -} - -func (_c *Dispatcher_PrepareMessage_Call) RunAndReturn(run func(context.Context, notification.Notification) ([]notification.Message, []log.Notification, bool, error)) *Dispatcher_PrepareMessage_Call { - _c.Call.Return(run) - return _c -} - -// PrepareMessageV2 provides a mock function with given fields: ctx, n -func (_m *Dispatcher) PrepareMessageV2(ctx context.Context, n notification.Notification) ([]notification.Message, []log.Notification, bool, error) { - ret := _m.Called(ctx, n) +// Dispatch provides a mock function with given fields: ctx, ns +func (_m *Dispatcher) Dispatch(ctx context.Context, ns []notification.Notification) ([]string, error) { + ret := _m.Called(ctx, ns) if len(ret) == 0 { - panic("no return value specified for PrepareMessageV2") + panic("no return value specified for Dispatch") } - var r0 []notification.Message - var r1 []log.Notification - var r2 bool - var r3 error - if rf, ok := ret.Get(0).(func(context.Context, notification.Notification) ([]notification.Message, []log.Notification, bool, error)); ok { - return rf(ctx, n) + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []notification.Notification) ([]string, error)); ok { + return rf(ctx, ns) } - if rf, ok := ret.Get(0).(func(context.Context, notification.Notification) []notification.Message); ok { - r0 = rf(ctx, n) + if rf, ok := ret.Get(0).(func(context.Context, []notification.Notification) []string); ok { + r0 = rf(ctx, ns) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]notification.Message) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, notification.Notification) []log.Notification); ok { - r1 = rf(ctx, n) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).([]log.Notification) + r0 = ret.Get(0).([]string) } } - if rf, ok := ret.Get(2).(func(context.Context, notification.Notification) bool); ok { - r2 = rf(ctx, n) - } else { - r2 = ret.Get(2).(bool) - } - - if rf, ok := ret.Get(3).(func(context.Context, notification.Notification) error); ok { - r3 = rf(ctx, n) + if rf, ok := ret.Get(1).(func(context.Context, []notification.Notification) error); ok { + r1 = rf(ctx, ns) } else { - r3 = ret.Error(3) + r1 = ret.Error(1) } - return r0, r1, r2, r3 + return r0, r1 } -// Dispatcher_PrepareMessageV2_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrepareMessageV2' -type Dispatcher_PrepareMessageV2_Call struct { +// Dispatcher_Dispatch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Dispatch' +type Dispatcher_Dispatch_Call struct { *mock.Call } -// PrepareMessageV2 is a helper method to define mock.On call +// Dispatch is a helper method to define mock.On call // - ctx context.Context -// - n notification.Notification -func (_e *Dispatcher_Expecter) PrepareMessageV2(ctx interface{}, n interface{}) *Dispatcher_PrepareMessageV2_Call { - return &Dispatcher_PrepareMessageV2_Call{Call: _e.mock.On("PrepareMessageV2", ctx, n)} +// - ns []notification.Notification +func (_e *Dispatcher_Expecter) Dispatch(ctx interface{}, ns interface{}) *Dispatcher_Dispatch_Call { + return &Dispatcher_Dispatch_Call{Call: _e.mock.On("Dispatch", ctx, ns)} } -func (_c *Dispatcher_PrepareMessageV2_Call) Run(run func(ctx context.Context, n notification.Notification)) *Dispatcher_PrepareMessageV2_Call { +func (_c *Dispatcher_Dispatch_Call) Run(run func(ctx context.Context, ns []notification.Notification)) *Dispatcher_Dispatch_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(notification.Notification)) + run(args[0].(context.Context), args[1].([]notification.Notification)) }) return _c } -func (_c *Dispatcher_PrepareMessageV2_Call) Return(_a0 []notification.Message, _a1 []log.Notification, _a2 bool, _a3 error) *Dispatcher_PrepareMessageV2_Call { - _c.Call.Return(_a0, _a1, _a2, _a3) +func (_c *Dispatcher_Dispatch_Call) Return(_a0 []string, _a1 error) *Dispatcher_Dispatch_Call { + _c.Call.Return(_a0, _a1) return _c } -func (_c *Dispatcher_PrepareMessageV2_Call) RunAndReturn(run func(context.Context, notification.Notification) ([]notification.Message, []log.Notification, bool, error)) *Dispatcher_PrepareMessageV2_Call { +func (_c *Dispatcher_Dispatch_Call) RunAndReturn(run func(context.Context, []notification.Notification) ([]string, error)) *Dispatcher_Dispatch_Call { _c.Call.Return(run) return _c } diff --git a/core/notification/mocks/notification_repository.go b/core/notification/mocks/notification_repository.go index 0a7ac096..ce27a9a3 100644 --- a/core/notification/mocks/notification_repository.go +++ b/core/notification/mocks/notification_repository.go @@ -22,6 +22,65 @@ func (_m *Repository) EXPECT() *Repository_Expecter { return &Repository_Expecter{mock: &_m.Mock} } +// BulkCreate provides a mock function with given fields: _a0, _a1 +func (_m *Repository) BulkCreate(_a0 context.Context, _a1 []notification.Notification) ([]notification.Notification, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for BulkCreate") + } + + var r0 []notification.Notification + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []notification.Notification) ([]notification.Notification, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, []notification.Notification) []notification.Notification); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]notification.Notification) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []notification.Notification) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Repository_BulkCreate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BulkCreate' +type Repository_BulkCreate_Call struct { + *mock.Call +} + +// BulkCreate is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 []notification.Notification +func (_e *Repository_Expecter) BulkCreate(_a0 interface{}, _a1 interface{}) *Repository_BulkCreate_Call { + return &Repository_BulkCreate_Call{Call: _e.mock.On("BulkCreate", _a0, _a1)} +} + +func (_c *Repository_BulkCreate_Call) Run(run func(_a0 context.Context, _a1 []notification.Notification)) *Repository_BulkCreate_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].([]notification.Notification)) + }) + return _c +} + +func (_c *Repository_BulkCreate_Call) Return(_a0 []notification.Notification, _a1 error) *Repository_BulkCreate_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Repository_BulkCreate_Call) RunAndReturn(run func(context.Context, []notification.Notification) ([]notification.Notification, error)) *Repository_BulkCreate_Call { + _c.Call.Return(run) + return _c +} + // Commit provides a mock function with given fields: ctx func (_m *Repository) Commit(ctx context.Context) error { ret := _m.Called(ctx) diff --git a/core/notification/mocks/router.go b/core/notification/mocks/router.go new file mode 100644 index 00000000..216db6ad --- /dev/null +++ b/core/notification/mocks/router.go @@ -0,0 +1,257 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + log "github.com/goto/siren/core/log" + mock "github.com/stretchr/testify/mock" + + notification "github.com/goto/siren/core/notification" +) + +// Router is an autogenerated mock type for the Router type +type Router struct { + mock.Mock +} + +type Router_Expecter struct { + mock *mock.Mock +} + +func (_m *Router) EXPECT() *Router_Expecter { + return &Router_Expecter{mock: &_m.Mock} +} + +// PrepareMessage provides a mock function with given fields: ctx, n +func (_m *Router) PrepareMessage(ctx context.Context, n notification.Notification) ([]notification.Message, []log.Notification, bool, error) { + ret := _m.Called(ctx, n) + + if len(ret) == 0 { + panic("no return value specified for PrepareMessage") + } + + var r0 []notification.Message + var r1 []log.Notification + var r2 bool + var r3 error + if rf, ok := ret.Get(0).(func(context.Context, notification.Notification) ([]notification.Message, []log.Notification, bool, error)); ok { + return rf(ctx, n) + } + if rf, ok := ret.Get(0).(func(context.Context, notification.Notification) []notification.Message); ok { + r0 = rf(ctx, n) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]notification.Message) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, notification.Notification) []log.Notification); ok { + r1 = rf(ctx, n) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]log.Notification) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, notification.Notification) bool); ok { + r2 = rf(ctx, n) + } else { + r2 = ret.Get(2).(bool) + } + + if rf, ok := ret.Get(3).(func(context.Context, notification.Notification) error); ok { + r3 = rf(ctx, n) + } else { + r3 = ret.Error(3) + } + + return r0, r1, r2, r3 +} + +// Router_PrepareMessage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrepareMessage' +type Router_PrepareMessage_Call struct { + *mock.Call +} + +// PrepareMessage is a helper method to define mock.On call +// - ctx context.Context +// - n notification.Notification +func (_e *Router_Expecter) PrepareMessage(ctx interface{}, n interface{}) *Router_PrepareMessage_Call { + return &Router_PrepareMessage_Call{Call: _e.mock.On("PrepareMessage", ctx, n)} +} + +func (_c *Router_PrepareMessage_Call) Run(run func(ctx context.Context, n notification.Notification)) *Router_PrepareMessage_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(notification.Notification)) + }) + return _c +} + +func (_c *Router_PrepareMessage_Call) Return(_a0 []notification.Message, _a1 []log.Notification, _a2 bool, _a3 error) *Router_PrepareMessage_Call { + _c.Call.Return(_a0, _a1, _a2, _a3) + return _c +} + +func (_c *Router_PrepareMessage_Call) RunAndReturn(run func(context.Context, notification.Notification) ([]notification.Message, []log.Notification, bool, error)) *Router_PrepareMessage_Call { + _c.Call.Return(run) + return _c +} + +// PrepareMessageV2 provides a mock function with given fields: ctx, n +func (_m *Router) PrepareMessageV2(ctx context.Context, n notification.Notification) ([]notification.Message, []log.Notification, bool, error) { + ret := _m.Called(ctx, n) + + if len(ret) == 0 { + panic("no return value specified for PrepareMessageV2") + } + + var r0 []notification.Message + var r1 []log.Notification + var r2 bool + var r3 error + if rf, ok := ret.Get(0).(func(context.Context, notification.Notification) ([]notification.Message, []log.Notification, bool, error)); ok { + return rf(ctx, n) + } + if rf, ok := ret.Get(0).(func(context.Context, notification.Notification) []notification.Message); ok { + r0 = rf(ctx, n) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]notification.Message) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, notification.Notification) []log.Notification); ok { + r1 = rf(ctx, n) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]log.Notification) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, notification.Notification) bool); ok { + r2 = rf(ctx, n) + } else { + r2 = ret.Get(2).(bool) + } + + if rf, ok := ret.Get(3).(func(context.Context, notification.Notification) error); ok { + r3 = rf(ctx, n) + } else { + r3 = ret.Error(3) + } + + return r0, r1, r2, r3 +} + +// Router_PrepareMessageV2_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrepareMessageV2' +type Router_PrepareMessageV2_Call struct { + *mock.Call +} + +// PrepareMessageV2 is a helper method to define mock.On call +// - ctx context.Context +// - n notification.Notification +func (_e *Router_Expecter) PrepareMessageV2(ctx interface{}, n interface{}) *Router_PrepareMessageV2_Call { + return &Router_PrepareMessageV2_Call{Call: _e.mock.On("PrepareMessageV2", ctx, n)} +} + +func (_c *Router_PrepareMessageV2_Call) Run(run func(ctx context.Context, n notification.Notification)) *Router_PrepareMessageV2_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(notification.Notification)) + }) + return _c +} + +func (_c *Router_PrepareMessageV2_Call) Return(_a0 []notification.Message, _a1 []log.Notification, _a2 bool, _a3 error) *Router_PrepareMessageV2_Call { + _c.Call.Return(_a0, _a1, _a2, _a3) + return _c +} + +func (_c *Router_PrepareMessageV2_Call) RunAndReturn(run func(context.Context, notification.Notification) ([]notification.Message, []log.Notification, bool, error)) *Router_PrepareMessageV2_Call { + _c.Call.Return(run) + return _c +} + +// PrepareMetaMessages provides a mock function with given fields: ctx, n +func (_m *Router) PrepareMetaMessages(ctx context.Context, n notification.Notification) ([]notification.MetaMessage, []log.Notification, error) { + ret := _m.Called(ctx, n) + + if len(ret) == 0 { + panic("no return value specified for PrepareMetaMessages") + } + + var r0 []notification.MetaMessage + var r1 []log.Notification + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, notification.Notification) ([]notification.MetaMessage, []log.Notification, error)); ok { + return rf(ctx, n) + } + if rf, ok := ret.Get(0).(func(context.Context, notification.Notification) []notification.MetaMessage); ok { + r0 = rf(ctx, n) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]notification.MetaMessage) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, notification.Notification) []log.Notification); ok { + r1 = rf(ctx, n) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]log.Notification) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, notification.Notification) error); ok { + r2 = rf(ctx, n) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// Router_PrepareMetaMessages_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrepareMetaMessages' +type Router_PrepareMetaMessages_Call struct { + *mock.Call +} + +// PrepareMetaMessages is a helper method to define mock.On call +// - ctx context.Context +// - n notification.Notification +func (_e *Router_Expecter) PrepareMetaMessages(ctx interface{}, n interface{}) *Router_PrepareMetaMessages_Call { + return &Router_PrepareMetaMessages_Call{Call: _e.mock.On("PrepareMetaMessages", ctx, n)} +} + +func (_c *Router_PrepareMetaMessages_Call) Run(run func(ctx context.Context, n notification.Notification)) *Router_PrepareMetaMessages_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(notification.Notification)) + }) + return _c +} + +func (_c *Router_PrepareMetaMessages_Call) Return(metaMessages []notification.MetaMessage, notificationLogs []log.Notification, err error) *Router_PrepareMetaMessages_Call { + _c.Call.Return(metaMessages, notificationLogs, err) + return _c +} + +func (_c *Router_PrepareMetaMessages_Call) RunAndReturn(run func(context.Context, notification.Notification) ([]notification.MetaMessage, []log.Notification, error)) *Router_PrepareMetaMessages_Call { + _c.Call.Return(run) + return _c +} + +// NewRouter creates a new instance of Router. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewRouter(t interface { + mock.TestingT + Cleanup(func()) +}) *Router { + mock := &Router{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/notification/notification.go b/core/notification/notification.go index 3eed8192..6c7c8848 100644 --- a/core/notification/notification.go +++ b/core/notification/notification.go @@ -4,21 +4,26 @@ import ( "context" "time" + "github.com/goto/siren/core/subscription" "github.com/goto/siren/pkg/errors" ) const ( ValidDurationRequestKey string = "valid_duration" - FlowReceiver string = "receiver" - FlowSubscriber string = "subscriber" + RouterReceiver string = "receiver" + RouterSubscriber string = "subscriber" TypeAlert string = "alert" TypeEvent string = "event" + + DispatchKindBulkNotification = "bulk_notification" + DispatchKindSingleNotification = "single_notification" ) type Repository interface { Transactor + BulkCreate(context.Context, []Notification) ([]Notification, error) Create(context.Context, Notification) (Notification, error) List(context.Context, Filter) ([]Notification, error) } @@ -59,13 +64,13 @@ func (n *Notification) EnrichID(id string) { n.Data["id"] = id } -func (n Notification) Validate(flow string) error { - if flow == FlowReceiver { +func (n Notification) Validate(routerKind string) error { + if routerKind == RouterReceiver { if len(n.ReceiverSelectors) != 0 { return nil } return errors.ErrInvalid.WithMsgf("notification type receiver should have receiver_selectors: %v", n) - } else if flow == FlowSubscriber { + } else if routerKind == RouterSubscriber { if len(n.Labels) != 0 { return nil } @@ -74,3 +79,18 @@ func (n Notification) Validate(flow string) error { return errors.ErrInvalid.WithMsgf("invalid notification type: %v", n) } + +func (n Notification) MetaMessage(receiverView subscription.ReceiverView) MetaMessage { + return MetaMessage{ + ReceiverID: receiverView.ID, + SubscriptionIDs: []uint64{receiverView.SubscriptionID}, + ReceiverType: receiverView.Type, + NotificationIDs: []string{n.ID}, + NotificationType: n.Type, + ReceiverConfigs: receiverView.Configurations, + Data: n.Data, + ValidDuration: n.ValidDuration, + Template: n.Template, + Labels: n.Labels, + } +} diff --git a/core/notification/notification_test.go b/core/notification/notification_test.go index 99fd2bd9..6da50a90 100644 --- a/core/notification/notification_test.go +++ b/core/notification/notification_test.go @@ -23,7 +23,7 @@ func TestNotification_Validate(t *testing.T) { }, { name: "should return error if flow receiver but has no receiver_selectors", - Flow: notification.FlowReceiver, + Flow: notification.RouterReceiver, n: notification.Notification{ Labels: map[string]string{ "labelkey1": "value1", @@ -36,7 +36,7 @@ func TestNotification_Validate(t *testing.T) { }, { name: "should return nil error if flow receiver and receiver_selectors exist", - Flow: notification.FlowReceiver, + Flow: notification.RouterReceiver, n: notification.Notification{ Labels: map[string]string{ "receiver_id": "2", @@ -50,7 +50,7 @@ func TestNotification_Validate(t *testing.T) { }, { name: "should return error if flow subscriber but has no kv labels", - Flow: notification.FlowSubscriber, + Flow: notification.RouterSubscriber, n: notification.Notification{ Data: map[string]any{ "varkey1": "value1", @@ -60,7 +60,7 @@ func TestNotification_Validate(t *testing.T) { }, { name: "should return nil error if flow subscriber and has kv labels", - Flow: notification.FlowSubscriber, + Flow: notification.RouterSubscriber, n: notification.Notification{ Labels: map[string]string{ "receiver_id": "xxx", diff --git a/core/notification/router_receiver_service.go b/core/notification/router_receiver_service.go new file mode 100644 index 00000000..649d14e9 --- /dev/null +++ b/core/notification/router_receiver_service.go @@ -0,0 +1,133 @@ +package notification + +import ( + "context" + + "github.com/goto/siren/core/log" + "github.com/goto/siren/core/receiver" + "github.com/goto/siren/core/subscription" + "github.com/goto/siren/pkg/errors" +) + +type RouterReceiverService struct { + deps Deps + notifierPlugins map[string]Notifier +} + +func NewRouterReceiverService( + deps Deps, + notifierPlugins map[string]Notifier, +) *RouterReceiverService { + return &RouterReceiverService{ + deps: deps, + notifierPlugins: notifierPlugins, + } +} + +func (s *RouterReceiverService) getNotifierPlugin(receiverType string) (Notifier, error) { + notifierPlugin, exist := s.notifierPlugins[receiverType] + if !exist { + return nil, errors.ErrInvalid.WithMsgf("unsupported receiver type: %q", receiverType) + } + return notifierPlugin, nil +} + +func (s *RouterReceiverService) PrepareMetaMessages(ctx context.Context, n Notification) (metaMessages []MetaMessage, notificationLogs []log.Notification, err error) { + if len(n.ReceiverSelectors) > s.deps.Cfg.MaxNumReceiverSelectors { + return nil, nil, errors.ErrInvalid.WithMsgf("number of receiver selectors should be less than or equal threshold %d", s.deps.Cfg.MaxNumReceiverSelectors) + } + + rcvs, err := s.deps.ReceiverService.List(ctx, receiver.Filter{ + MultipleLabels: n.ReceiverSelectors, + Expanded: true, + }) + if err != nil { + return nil, nil, err + } + + if len(rcvs) == 0 { + return nil, nil, errors.ErrNotFound + } + + for _, rcv := range rcvs { + var rcvView = &subscription.ReceiverView{} + rcvView.FromReceiver(rcv) + metaMessages = append(metaMessages, n.MetaMessage(*rcvView)) + + notificationLogs = append(notificationLogs, log.Notification{ + NamespaceID: n.NamespaceID, + NotificationID: n.ID, + ReceiverID: rcv.ID, + AlertIDs: n.AlertIDs, + }) + } + + var metaMessagesNum = len(metaMessages) + if metaMessagesNum > s.deps.Cfg.MaxMessagesReceiverFlow { + return nil, nil, errors.ErrInvalid.WithMsgf("sending %d messages exceed max messages receiver flow threshold %d. this will spam and broadcast to %d channel. found %d receiver selectors passed, you might want to check your receiver selectors configuration", metaMessagesNum, s.deps.Cfg.MaxMessagesReceiverFlow, metaMessagesNum, len(n.ReceiverSelectors)) + } + + return metaMessages, notificationLogs, nil +} + +func (s *RouterReceiverService) PrepareMessageV2(ctx context.Context, n Notification) ([]Message, []log.Notification, bool, error) { + return s.PrepareMessage(ctx, n) +} + +func (s *RouterReceiverService) PrepareMessage(ctx context.Context, n Notification) ([]Message, []log.Notification, bool, error) { + + var notificationLogs []log.Notification + + if len(n.ReceiverSelectors) > s.deps.Cfg.MaxNumReceiverSelectors { + return nil, nil, false, errors.ErrInvalid.WithMsgf("number of receiver selectors should be less than or equal threshold %d", s.deps.Cfg.MaxNumReceiverSelectors) + } + + rcvs, err := s.deps.ReceiverService.List(ctx, receiver.Filter{ + MultipleLabels: n.ReceiverSelectors, + Expanded: true, + }) + if err != nil { + return nil, nil, false, err + } + + if len(rcvs) == 0 { + return nil, nil, false, errors.ErrNotFound + } + + var messages []Message + + for _, rcv := range rcvs { + notifierPlugin, err := s.getNotifierPlugin(rcv.Type) + if err != nil { + return nil, nil, false, errors.ErrInvalid.WithMsgf("invalid receiver type: %s", err.Error()) + } + + message, err := InitMessage( + ctx, + notifierPlugin, + s.deps.TemplateService, + n, + rcv.Type, + rcv.Configurations, + InitWithExpiryDuration(n.ValidDuration), + ) + if err != nil { + return nil, nil, false, err + } + + messages = append(messages, message) + notificationLogs = append(notificationLogs, log.Notification{ + NamespaceID: n.NamespaceID, + NotificationID: n.ID, + ReceiverID: rcv.ID, + AlertIDs: n.AlertIDs, + }) + } + + var messagesNum = len(messages) + if messagesNum > s.deps.Cfg.MaxMessagesReceiverFlow { + return nil, nil, false, errors.ErrInvalid.WithMsgf("sending %d messages exceed max messages receiver flow threshold %d. this will spam and broadcast to %d channel. found %d receiver selectors passed, you might want to check your receiver selectors configuration", messagesNum, s.deps.Cfg.MaxMessagesReceiverFlow, messagesNum, len(n.ReceiverSelectors)) + } + + return messages, notificationLogs, false, nil +} diff --git a/core/notification/dispatch_receiver_service_test.go b/core/notification/router_receiver_service_test.go similarity index 79% rename from core/notification/dispatch_receiver_service_test.go rename to core/notification/router_receiver_service_test.go index 4a8c516b..b44c6f8b 100644 --- a/core/notification/dispatch_receiver_service_test.go +++ b/core/notification/router_receiver_service_test.go @@ -6,6 +6,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + saltlog "github.com/goto/salt/log" "github.com/goto/siren/core/log" "github.com/goto/siren/core/notification" "github.com/goto/siren/core/notification/mocks" @@ -15,7 +16,7 @@ import ( "github.com/stretchr/testify/mock" ) -func TestDispatchReceiverService_PrepareMessage(t *testing.T) { +func TestRouterReceiverService_PrepareMessage(t *testing.T) { var notificationID = "1234-5678" tests := []struct { name string @@ -63,7 +64,7 @@ func TestDispatchReceiverService_PrepareMessage(t *testing.T) { rs.EXPECT().List(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("receiver.Filter")).Return([]receiver.Receiver{ { ID: 11, - Type: testPluginType}, + Type: testType}, }, nil) n.EXPECT().PreHookQueueTransformConfigs(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("map[string]interface {}")).Return(nil, errors.New("some error")) }, @@ -83,7 +84,7 @@ func TestDispatchReceiverService_PrepareMessage(t *testing.T) { rs.EXPECT().List(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("receiver.Filter")).Return([]receiver.Receiver{ { ID: 11, - Type: testPluginType, + Type: testType, }, }, nil) n.EXPECT().PreHookQueueTransformConfigs(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("map[string]interface {}")).Return(map[string]any{}, nil) @@ -105,7 +106,7 @@ func TestDispatchReceiverService_PrepareMessage(t *testing.T) { rs.EXPECT().List(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("receiver.Filter")).Return([]receiver.Receiver{ { ID: 11, - Type: testPluginType, + Type: testType, }, }, nil) n.EXPECT().PreHookQueueTransformConfigs(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("map[string]interface {}")).Return(map[string]any{}, nil) @@ -113,12 +114,12 @@ func TestDispatchReceiverService_PrepareMessage(t *testing.T) { }, want: []notification.Message{ { - Status: notification.MessageStatusEnqueued, - NotificationID: notificationID, - ReceiverType: testPluginType, - Configs: map[string]any{}, - Details: map[string]any{"notification_type": string("")}, - MaxTries: 3, + Status: notification.MessageStatusEnqueued, + NotificationIDs: []string{notificationID}, + ReceiverType: testType, + Configs: map[string]any{}, + Details: map[string]any{"notification_type": string("")}, + MaxTries: 3, }, }, want1: []log.Notification{{ @@ -134,34 +135,38 @@ func TestDispatchReceiverService_PrepareMessage(t *testing.T) { mockNotifier = new(mocks.Notifier) mockTemplateService = new(mocks.TemplateService) ) - s := notification.NewDispatchReceiverService( - notification.DispatchReceiverConfig{ - MaxMessagesReceiverFlow: 10, - MaxNumReceiverSelectors: 10, + s := notification.NewRouterReceiverService( + notification.Deps{ + Cfg: notification.Config{ + MaxMessagesReceiverFlow: 10, + MaxNumReceiverSelectors: 10, + }, + Logger: saltlog.NewNoop(), + ReceiverService: mockReceiverService, + TemplateService: mockTemplateService, }, - mockReceiverService, - mockTemplateService, map[string]notification.Notifier{ - testPluginType: mockNotifier, - }) + testType: mockNotifier, + }, + ) if tt.setup != nil { tt.setup(mockReceiverService, mockNotifier) } got, got1, got2, err := s.PrepareMessage(context.TODO(), tt.n) if (err != nil) != tt.wantErr { - t.Errorf("DispatchReceiverService.PrepareMessage() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("RouterReceiverService.PrepareMessage() error = %v, wantErr %v", err, tt.wantErr) return } if diff := cmp.Diff(got, tt.want, cmpopts.IgnoreFields(notification.Message{}, "ID", "CreatedAt", "UpdatedAt"), cmpopts.IgnoreUnexported(notification.Message{})); diff != "" { - t.Errorf("DispatchReceiverService.PrepareMessage() diff = %v", diff) + t.Errorf("RouterReceiverService.PrepareMessage() diff = %v", diff) } if diff := cmp.Diff(got1, tt.want1); diff != "" { - t.Errorf("DispatchReceiverService.PrepareMessage() diff = %v", diff) + t.Errorf("RouterReceiverService.PrepareMessage() diff = %v", diff) } if got2 != tt.want2 { - t.Errorf("DispatchReceiverService.PrepareMessage() got2 = %v, want %v", got2, tt.want2) + t.Errorf("RouterReceiverService.PrepareMessage() got2 = %v, want %v", got2, tt.want2) } }) } diff --git a/core/notification/dispatch_subscriber_service.go b/core/notification/router_subscriber_service.go similarity index 50% rename from core/notification/dispatch_subscriber_service.go rename to core/notification/router_subscriber_service.go index 0d75f73d..5b4343bc 100644 --- a/core/notification/dispatch_subscriber_service.go +++ b/core/notification/router_subscriber_service.go @@ -2,9 +2,9 @@ package notification import ( "context" + "encoding/json" "fmt" - saltlog "github.com/goto/salt/log" "github.com/goto/siren/core/log" "github.com/goto/siren/core/silence" "github.com/goto/siren/pkg/errors" @@ -14,51 +14,39 @@ import ( ) const ( - metricDispatchSubscriberAttributePrepareMessage = "preparemessage.status" - metricDispatchSubscriberAttributePrepareSuccess = "preparemessage.success" - - metricDispatchSubscriberStatusMatchError = "MATCH_ERROR" - metricDispatchSubscriberStatusMatchNotFound = "MATCH_NOT_FOUND" - metricDispatchSubscriberStatusMessageInitError = "MESSAGE_INIT_ERROR" - metricDispatchSubscriberStatusNotifierError = "NOTIFIER_ERROR" - metricDispatchSubscriberStatusSuccess = "SUCCESS" + metricRouterSubscriberAttributePrepareMessage = "preparemessage.status" + metricRouterSubscriberAttributePrepareSuccess = "preparemessage.success" + + metricRouterSubscriberStatusMatchError = "MATCH_ERROR" + metricRouterSubscriberStatusMatchNotFound = "MATCH_NOT_FOUND" + metricRouterSubscriberStatusMessageInitError = "MESSAGE_INIT_ERROR" + metricRouterSubscriberStatusNotifierError = "NOTIFIER_ERROR" + metricRouterSubscriberStatusSuccess = "SUCCESS" ) -type DispatchSubscriberService struct { - logger saltlog.Logger - subscriptionService SubscriptionService - silenceService SilenceService - templateService TemplateService - notifierPlugins map[string]Notifier - metricCounterDispatchSubscriber metric.Int64Counter - enableSilenceFeature bool +type RouterSubscriberService struct { + deps Deps + notifierPlugins map[string]Notifier + metricCounterRouterSubscriber metric.Int64Counter } -func NewDispatchSubscriberService( - logger saltlog.Logger, - subscriptionService SubscriptionService, - silenceService SilenceService, - templateService TemplateService, +func NewRouterSubscriberService( + deps Deps, notifierPlugins map[string]Notifier, - enableSilenceFeature bool, -) *DispatchSubscriberService { - metricCounterDispatchSubscriber, err := otel.Meter("github.com/goto/siren/core/notification"). +) *RouterSubscriberService { + metricCounterRouterSubscriber, err := otel.Meter("github.com/goto/siren/core/notification"). Int64Counter("siren.notification.dispatch.subscriber") if err != nil { otel.Handle(err) } - return &DispatchSubscriberService{ - logger: logger, - subscriptionService: subscriptionService, - silenceService: silenceService, - notifierPlugins: notifierPlugins, - enableSilenceFeature: enableSilenceFeature, - templateService: templateService, - metricCounterDispatchSubscriber: metricCounterDispatchSubscriber, + return &RouterSubscriberService{ + deps: deps, + notifierPlugins: notifierPlugins, + metricCounterRouterSubscriber: metricCounterRouterSubscriber, } } -func (s *DispatchSubscriberService) getNotifierPlugin(receiverType string) (Notifier, error) { +func (s *RouterSubscriberService) getNotifierPlugin(receiverType string) (Notifier, error) { notifierPlugin, exist := s.notifierPlugins[receiverType] if !exist { return nil, errors.ErrInvalid.WithMsgf("unsupported receiver type: %q", receiverType) @@ -66,7 +54,7 @@ func (s *DispatchSubscriberService) getNotifierPlugin(receiverType string) (Noti return notifierPlugin, nil } -func (s *DispatchSubscriberService) PrepareMessage(ctx context.Context, n Notification) ([]Message, []log.Notification, bool, error) { +func (s *RouterSubscriberService) PrepareMessage(ctx context.Context, n Notification) ([]Message, []log.Notification, bool, error) { var ( messages = make([]Message, 0) @@ -74,29 +62,28 @@ func (s *DispatchSubscriberService) PrepareMessage(ctx context.Context, n Notifi hasSilenced bool ) - subscriptions, err := s.subscriptionService.MatchByLabels(ctx, n.NamespaceID, n.Labels) + subscriptions, err := s.deps.SubscriptionService.MatchByLabels(ctx, n.NamespaceID, n.Labels) if err != nil { return nil, nil, false, err } if len(subscriptions) == 0 { - // telemetry.IncrementInt64Counter(ctx, telemetry.MetricNotificationSubscriberNotFound) return nil, nil, false, errors.ErrInvalid.WithMsgf("not matching any subscription") } for _, sub := range subscriptions { if len(sub.Receivers) == 0 { - s.logger.Warn(fmt.Sprintf("invalid subscription with id %d, no receiver found", sub.ID)) + s.deps.Logger.Warn(fmt.Sprintf("invalid subscription with id %d, no receiver found", sub.ID)) continue } var silences []silence.Silence // Reliability of silence feature need to be tested more - if s.enableSilenceFeature { + if s.deps.Cfg.EnableSilenceFeature { // try silencing by labels - silences, err = s.silenceService.List(ctx, silence.Filter{ + silences, err = s.deps.SilenceService.List(ctx, silence.Filter{ NamespaceID: n.NamespaceID, SubscriptionMatch: sub.Match, }) @@ -121,14 +108,14 @@ func (s *DispatchSubscriberService) PrepareMessage(ctx context.Context, n Notifi SilenceIDs: silenceIDs, }) - s.logger.Info(fmt.Sprintf("notification '%s' of alert ids '%v' is being silenced by labels '%v'", n.ID, n.AlertIDs, silences)) + s.deps.Logger.Info(fmt.Sprintf("notification '%s' of alert ids '%v' is being silenced by labels '%v'", n.ID, n.AlertIDs, silences)) continue } // Reliability of silence feature need to be tested more - if s.enableSilenceFeature { + if s.deps.Cfg.EnableSilenceFeature { // subscription not being silenced by label - silences, err = s.silenceService.List(ctx, silence.Filter{ + silences, err = s.deps.SilenceService.List(ctx, silence.Filter{ NamespaceID: n.NamespaceID, SubscriptionID: sub.ID, }) @@ -171,7 +158,7 @@ func (s *DispatchSubscriberService) PrepareMessage(ctx context.Context, n Notifi message, err := InitMessage( ctx, notifierPlugin, - s.templateService, + s.deps.TemplateService, n, rcv.Type, rcv.Configuration, @@ -195,44 +182,44 @@ func (s *DispatchSubscriberService) PrepareMessage(ctx context.Context, n Notifi return messages, notificationLogs, hasSilenced, nil } -func (s *DispatchSubscriberService) PrepareMessageV2(ctx context.Context, n Notification) (messages []Message, notificationLogs []log.Notification, hasSilenced bool, err error) { - var metricStatus = metricDispatchSubscriberStatusSuccess +func (s *RouterSubscriberService) PrepareMessageV2(ctx context.Context, n Notification) (messages []Message, notificationLogs []log.Notification, hasSilenced bool, err error) { + var metricStatus = metricRouterSubscriberStatusSuccess messages = make([]Message, 0) defer func() { - s.instrumentDispatchSubscription(ctx, metricDispatchSubscriberAttributePrepareMessage, metricStatus, err) + s.instrumentDispatchSubscription(ctx, metricRouterSubscriberAttributePrepareMessage, metricStatus, err) }() - receiversView, err := s.subscriptionService.MatchByLabelsV2(ctx, n.NamespaceID, n.Labels) + receiversView, err := s.deps.SubscriptionService.MatchByLabelsV2(ctx, n.NamespaceID, n.Labels) if err != nil { - metricStatus = metricDispatchSubscriberStatusMatchError + metricStatus = metricRouterSubscriberStatusMatchError return nil, nil, false, err } if len(receiversView) == 0 { - metricStatus = metricDispatchSubscriberStatusMatchNotFound + metricStatus = metricRouterSubscriberStatusMatchNotFound return nil, nil, false, errors.ErrInvalid.WithMsgf("not matching any subscription") } for _, rcv := range receiversView { notifierPlugin, err := s.getNotifierPlugin(rcv.Type) if err != nil { - metricStatus = metricDispatchSubscriberStatusNotifierError + metricStatus = metricRouterSubscriberStatusNotifierError return nil, nil, false, err } message, err := InitMessage( ctx, notifierPlugin, - s.templateService, + s.deps.TemplateService, n, rcv.Type, rcv.Configurations, InitWithExpiryDuration(n.ValidDuration), ) if err != nil { - metricStatus = metricDispatchSubscriberStatusMessageInitError + metricStatus = metricRouterSubscriberStatusMessageInitError return nil, nil, false, err } @@ -249,9 +236,48 @@ func (s *DispatchSubscriberService) PrepareMessageV2(ctx context.Context, n Noti return messages, notificationLogs, hasSilenced, nil } -func (s *DispatchSubscriberService) instrumentDispatchSubscription(ctx context.Context, attributeKey, attributeValue string, err error) { - s.metricCounterDispatchSubscriber.Add(ctx, 1, metric.WithAttributes( +func (s *RouterSubscriberService) instrumentDispatchSubscription(ctx context.Context, attributeKey, attributeValue string, err error) { + s.metricCounterRouterSubscriber.Add(ctx, 1, metric.WithAttributes( attribute.String(attributeKey, attributeValue), attribute.Bool("operation.success", err == nil), )) } + +func (s *RouterSubscriberService) PrepareMetaMessages(ctx context.Context, n Notification) (metaMessages []MetaMessage, notificationLogs []log.Notification, err error) { + var metricStatus = metricRouterSubscriberStatusSuccess + + defer func() { + s.instrumentDispatchSubscription(ctx, metricRouterSubscriberAttributePrepareMessage, metricStatus, err) + }() + + receiversView, err := s.deps.SubscriptionService.MatchByLabelsV2(ctx, n.NamespaceID, n.Labels) + if err != nil { + metricStatus = metricRouterSubscriberStatusMatchError + return nil, nil, err + } + + if len(receiversView) == 0 { + metricStatus = metricRouterSubscriberStatusMatchNotFound + errMessage := fmt.Sprintf("not matching any subscription for notification: %v", n) + nJson, err := json.MarshalIndent(n, "", " ") + if err == nil { + errMessage = fmt.Sprintf("not matching any subscription for notification: %s", string(nJson)) + } + return nil, nil, errors.ErrInvalid.WithMsgf(errMessage) + } + + for _, rcv := range receiversView { + metaMessages = append(metaMessages, n.MetaMessage(rcv)) + + // messages = append(messages, message) + notificationLogs = append(notificationLogs, log.Notification{ + NamespaceID: n.NamespaceID, + NotificationID: n.ID, + SubscriptionID: rcv.SubscriptionID, + ReceiverID: rcv.ID, + AlertIDs: n.AlertIDs, + }) + } + + return metaMessages, notificationLogs, nil +} diff --git a/core/notification/dispatch_subscriber_service_test.go b/core/notification/router_subscriber_service_test.go similarity index 86% rename from core/notification/dispatch_subscriber_service_test.go rename to core/notification/router_subscriber_service_test.go index 516ce553..e580ba1e 100644 --- a/core/notification/dispatch_subscriber_service_test.go +++ b/core/notification/router_subscriber_service_test.go @@ -18,7 +18,7 @@ import ( "github.com/stretchr/testify/mock" ) -func TestDispatchSubscriberService_PrepareMessage(t *testing.T) { +func TestRouterSubscriberService_PrepareMessage(t *testing.T) { tests := []struct { name string setup func(*mocks.SubscriptionService, *mocks.SilenceService, *mocks.Notifier) @@ -240,7 +240,7 @@ func TestDispatchSubscriberService_PrepareMessage(t *testing.T) { Receivers: []subscription.Receiver{ { ID: 1, - Type: testPluginType, + Type: testType, }, }, }, @@ -262,6 +262,7 @@ func TestDispatchSubscriberService_PrepareMessage(t *testing.T) { { name: "should return no error if all flow passed and no silences", n: notification.Notification{ + ID: "aa", NamespaceID: 1, Type: receiver.TypeHTTP, Template: template.ReservedName_SystemDefault, @@ -277,7 +278,7 @@ func TestDispatchSubscriberService_PrepareMessage(t *testing.T) { Receivers: []subscription.Receiver{ { ID: 1, - Type: testPluginType, + Type: testType, }, }, }, @@ -297,14 +298,15 @@ func TestDispatchSubscriberService_PrepareMessage(t *testing.T) { }, want: []notification.Message{ { - Status: notification.MessageStatusEnqueued, - ReceiverType: testPluginType, - Configs: map[string]any{}, - Details: map[string]any{"notification_type": string("http")}, - MaxTries: 3, + Status: notification.MessageStatusEnqueued, + NotificationIDs: []string{"aa"}, + ReceiverType: testType, + Configs: map[string]any{}, + Details: map[string]any{"notification_type": string("http")}, + MaxTries: 3, }, }, - want1: []log.Notification{{NamespaceID: 1, SubscriptionID: 123, ReceiverID: 1}}, + want1: []log.Notification{{NamespaceID: 1, NotificationID: "aa", SubscriptionID: 123, ReceiverID: 1}}, want2: false, }, } @@ -316,15 +318,19 @@ func TestDispatchSubscriberService_PrepareMessage(t *testing.T) { mockNotifier = new(mocks.Notifier) mockTemplateService = new(mocks.TemplateService) ) - s := notification.NewDispatchSubscriberService( - saltlog.NewNoop(), - mockSubscriptionService, - mockSilenceService, - mockTemplateService, + s := notification.NewRouterSubscriberService( + notification.Deps{ + Cfg: notification.Config{ + EnableSilenceFeature: true, + }, + Logger: saltlog.NewNoop(), + SubscriptionService: mockSubscriptionService, + SilenceService: mockSilenceService, + TemplateService: mockTemplateService, + }, map[string]notification.Notifier{ - testPluginType: mockNotifier, + testType: mockNotifier, }, - true, ) if tt.setup != nil { @@ -333,25 +339,25 @@ func TestDispatchSubscriberService_PrepareMessage(t *testing.T) { got, got1, got2, err := s.PrepareMessage(context.TODO(), tt.n) if (err != nil) != tt.wantErr { - t.Errorf("DispatchSubscriberService.PrepareMessage() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("RouterSubscriberService.PrepareMessage() error = %v, wantErr %v", err, tt.wantErr) return } if diff := cmp.Diff(got, tt.want, cmpopts.IgnoreFields(notification.Message{}, "ID", "CreatedAt", "UpdatedAt"), cmpopts.IgnoreUnexported(notification.Message{})); diff != "" { - t.Errorf("DispatchSubscriberService.PrepareMessage() diff = %v", diff) + t.Errorf("RouterSubscriberService.PrepareMessage() diff = %v", diff) } if diff := cmp.Diff(got1, tt.want1); diff != "" { - t.Errorf("DispatchSubscriberService.PrepareMessage() diff = %v", diff) + t.Errorf("RouterSubscriberService.PrepareMessage() diff = %v", diff) } if got2 != tt.want2 { - t.Errorf("DispatchSubscriberService.PrepareMessage() got2 = %v, want %v", got2, tt.want2) + t.Errorf("RouterSubscriberService.PrepareMessage() got2 = %v, want %v", got2, tt.want2) } }) } } -func TestDispatchSubscriberService_PrepareMessageV2(t *testing.T) { +func TestRouterSubscriberService_PrepareMessageV2(t *testing.T) { tests := []struct { name string setup func(*mocks.SubscriptionService, *mocks.SilenceService, *mocks.Notifier) @@ -399,7 +405,7 @@ func TestDispatchSubscriberService_PrepareMessageV2(t *testing.T) { ss1.EXPECT().MatchByLabelsV2(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("uint64"), mock.AnythingOfType("map[string]string")).Return([]subscription.ReceiverView{ { ID: 1, - Type: testPluginType, + Type: testType, }, }, nil) ss2.EXPECT().List(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("silence.Filter")).Return(nil, errors.New("some error")) @@ -410,6 +416,7 @@ func TestDispatchSubscriberService_PrepareMessageV2(t *testing.T) { { name: "should return no error if all flow passed and no silences", n: notification.Notification{ + ID: "aa", NamespaceID: 1, Type: receiver.TypeHTTP, Template: template.ReservedName_SystemDefault, @@ -418,7 +425,7 @@ func TestDispatchSubscriberService_PrepareMessageV2(t *testing.T) { ss1.EXPECT().MatchByLabelsV2(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("uint64"), mock.AnythingOfType("map[string]string")).Return([]subscription.ReceiverView{ { ID: 1, - Type: testPluginType, + Type: testType, SubscriptionID: 123, }, }, nil) @@ -427,14 +434,15 @@ func TestDispatchSubscriberService_PrepareMessageV2(t *testing.T) { }, want: []notification.Message{ { - Status: notification.MessageStatusEnqueued, - ReceiverType: testPluginType, - Configs: map[string]any{}, - Details: map[string]any{"notification_type": string("http")}, - MaxTries: 3, + NotificationIDs: []string{"aa"}, + Status: notification.MessageStatusEnqueued, + ReceiverType: testType, + Configs: map[string]any{}, + Details: map[string]any{"notification_type": string("http")}, + MaxTries: 3, }, }, - want1: []log.Notification{{NamespaceID: 1, SubscriptionID: 123, ReceiverID: 1}}, + want1: []log.Notification{{NamespaceID: 1, NotificationID: "aa", SubscriptionID: 123, ReceiverID: 1}}, want2: false, }, } @@ -446,15 +454,19 @@ func TestDispatchSubscriberService_PrepareMessageV2(t *testing.T) { mockSilenceService = new(mocks.SilenceService) mockTemplateService = new(mocks.TemplateService) ) - s := notification.NewDispatchSubscriberService( - saltlog.NewNoop(), - mockSubscriptionService, - mockSilenceService, - mockTemplateService, + s := notification.NewRouterSubscriberService( + notification.Deps{ + Cfg: notification.Config{ + EnableSilenceFeature: true, + }, + Logger: saltlog.NewNoop(), + SubscriptionService: mockSubscriptionService, + SilenceService: mockSilenceService, + TemplateService: mockTemplateService, + }, map[string]notification.Notifier{ - testPluginType: mockNotifier, + testType: mockNotifier, }, - true, ) if tt.setup != nil { @@ -463,19 +475,19 @@ func TestDispatchSubscriberService_PrepareMessageV2(t *testing.T) { got, got1, got2, err := s.PrepareMessageV2(context.TODO(), tt.n) if (err != nil) != tt.wantErr { - t.Errorf("DispatchSubscriberService.PrepareMessageV2() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("RouterSubscriberService.PrepareMessageV2() error = %v, wantErr %v", err, tt.wantErr) return } if diff := cmp.Diff(got, tt.want, cmpopts.IgnoreFields(notification.Message{}, "ID", "CreatedAt", "UpdatedAt"), cmpopts.IgnoreUnexported(notification.Message{})); diff != "" { - t.Errorf("DispatchSubscriberService.PrepareMessageV2() diff = %v", diff) + t.Errorf("RouterSubscriberService.PrepareMessageV2() diff = %v", diff) } if diff := cmp.Diff(got1, tt.want1); diff != "" { - t.Errorf("DispatchSubscriberService.PrepareMessageV2() diff = %v", diff) + t.Errorf("RouterSubscriberService.PrepareMessageV2() diff = %v", diff) } if got2 != tt.want2 { - t.Errorf("DispatchSubscriberService.PrepareMessageV2() got2 = %v, want %v", got2, tt.want2) + t.Errorf("RouterSubscriberService.PrepareMessageV2() got2 = %v, want %v", got2, tt.want2) } }) } diff --git a/core/notification/service.go b/core/notification/service.go index ca1fe208..a81b4f6e 100644 --- a/core/notification/service.go +++ b/core/notification/service.go @@ -2,12 +2,10 @@ package notification import ( "context" - "fmt" "time" saltlog "github.com/goto/salt/log" - "github.com/goto/siren/core/alert" "github.com/goto/siren/core/log" "github.com/goto/siren/core/receiver" "github.com/goto/siren/core/silence" @@ -16,9 +14,14 @@ import ( "github.com/goto/siren/pkg/errors" ) -type Dispatcher interface { +type Router interface { PrepareMessage(ctx context.Context, n Notification) ([]Message, []log.Notification, bool, error) PrepareMessageV2(ctx context.Context, n Notification) ([]Message, []log.Notification, bool, error) + PrepareMetaMessages(ctx context.Context, n Notification) (metaMessages []MetaMessage, notificationLogs []log.Notification, err error) +} + +type Dispatcher interface { + Dispatch(ctx context.Context, ns []Notification) ([]string, error) } type SubscriptionService interface { @@ -34,8 +37,8 @@ type SilenceService interface { List(ctx context.Context, filter silence.Filter) ([]silence.Silence, error) } -type AlertService interface { - UpdateSilenceStatus(ctx context.Context, alertIDs []int64, hasSilenced bool, hasNonSilenced bool) error +type AlertRepository interface { + BulkUpdateSilence(context.Context, []int64, string) error } type LogService interface { @@ -48,211 +51,58 @@ type TemplateService interface { // Service is a service for notification domain type Service struct { - logger saltlog.Logger - cfg Config - q Queuer - idempotencyRepository IdempotencyRepository - logService LogService - repository Repository - receiverService ReceiverService - subscriptionService SubscriptionService - silenceService SilenceService - alertService AlertService - notifierPlugins map[string]Notifier - dispatcher map[string]Dispatcher - enableSilenceFeature bool + deps Deps + dispatchServiceMap map[string]Dispatcher } type Deps struct { - IdempotencyRepository IdempotencyRepository - LogService LogService - ReceiverService ReceiverService - TemplateService TemplateService - SubscriptionService SubscriptionService - SilenceService SilenceService - AlertService AlertService - DispatchReceiverService Dispatcher - DispatchSubscriberService Dispatcher + Cfg Config + Logger saltlog.Logger + Repository Repository + Q Queuer + IdempotencyRepository IdempotencyRepository + AlertRepository AlertRepository + LogService LogService + ReceiverService ReceiverService + TemplateService TemplateService + SubscriptionService SubscriptionService + SilenceService SilenceService } // NewService creates a new notification service func NewService( - logger saltlog.Logger, - cfg Config, - repository Repository, - q Queuer, - notifierPlugins map[string]Notifier, deps Deps, - enableSilenceFeature bool, + dispatchServiceMap map[string]Dispatcher, ) *Service { - var ( - dispatchReceiverService = deps.DispatchReceiverService - dispatchSubscriberService = deps.DispatchSubscriberService - ) - if deps.DispatchReceiverService == nil { - dispatchReceiverService = NewDispatchReceiverService(DispatchReceiverConfig{ - MaxMessagesReceiverFlow: cfg.MaxMessagesReceiverFlow, - MaxNumReceiverSelectors: cfg.MaxNumReceiverSelectors, - }, deps.ReceiverService, deps.TemplateService, notifierPlugins) - } - if deps.DispatchSubscriberService == nil { - dispatchSubscriberService = NewDispatchSubscriberService(logger, deps.SubscriptionService, deps.SilenceService, deps.TemplateService, notifierPlugins, enableSilenceFeature) + return &Service{ + deps: deps, + dispatchServiceMap: dispatchServiceMap, } - - ns := &Service{ - logger: logger, - cfg: cfg, - q: q, - repository: repository, - idempotencyRepository: deps.IdempotencyRepository, - logService: deps.LogService, - receiverService: deps.ReceiverService, - subscriptionService: deps.SubscriptionService, - silenceService: deps.SilenceService, - alertService: deps.AlertService, - dispatcher: map[string]Dispatcher{ - FlowReceiver: dispatchReceiverService, - FlowSubscriber: dispatchSubscriberService, - }, - notifierPlugins: notifierPlugins, - enableSilenceFeature: enableSilenceFeature, - } - - return ns } -func (s *Service) getDispatcherFlowService(notificationFlow string) (Dispatcher, error) { - selectedDispatcher, exist := s.dispatcher[notificationFlow] +func (s *Service) Dispatch(ctx context.Context, ns []Notification, dispatcherKind string) ([]string, error) { + ctx = s.deps.Repository.WithTransaction(ctx) + selectedDispatcher, exist := s.dispatchServiceMap[dispatcherKind] if !exist { - return nil, errors.ErrInvalid.WithMsgf("unsupported notification type: %q", notificationFlow) - } - return selectedDispatcher, nil -} - -func (s *Service) Dispatch(ctx context.Context, n Notification) (string, error) { - ctx = s.repository.WithTransaction(ctx) - no, err := s.repository.Create(ctx, n) - if err != nil { - if err := s.repository.Rollback(ctx, err); err != nil { - return "", err - } - return "", err - } - - n.EnrichID(no.ID) - - switch n.Type { - case TypeAlert: - if err := s.dispatchAlerts(ctx, n); err != nil { - if err := s.repository.Rollback(ctx, err); err != nil { - return "", err - } - return "", err - } - case TypeEvent: - if err := s.dispatchEvents(ctx, n); err != nil { - if err := s.repository.Rollback(ctx, err); err != nil { - return "", err - } - return "", err - } - default: - if err := s.repository.Rollback(ctx, err); err != nil { - return "", err - } - return "", errors.ErrInternal.WithMsgf("unknown notification type") - } - - if err := s.repository.Commit(ctx); err != nil { - return "", err + return nil, errors.ErrInvalid.WithMsgf("unsupported notification dispatcher: %q", dispatcherKind) } - - return n.ID, nil -} - -func (s *Service) dispatchByFlow(ctx context.Context, n Notification, flow string) error { - if err := n.Validate(flow); err != nil { - return err - } - - dispatcherService, err := s.getDispatcherFlowService(flow) + ids, err := selectedDispatcher.Dispatch(ctx, ns) if err != nil { - return err - } - - var ( - messages []Message - notificationLogs []log.Notification - hasSilenced bool - ) - if s.cfg.SubscriptionV2Enabled { - messages, notificationLogs, hasSilenced, err = dispatcherService.PrepareMessageV2(ctx, n) - if err != nil { - return err - } - } else { - messages, notificationLogs, hasSilenced, err = dispatcherService.PrepareMessage(ctx, n) - if err != nil { - return err - } - } - - if len(messages) == 0 && len(notificationLogs) == 0 { - return fmt.Errorf("something wrong and no messages will be sent with notification: %v", n) - } - - if err := s.logService.LogNotifications(ctx, notificationLogs...); err != nil { - return fmt.Errorf("failed logging notifications: %w", err) - } - - // Reliability of silence feature need to be tested more - if s.enableSilenceFeature { - if err := s.alertService.UpdateSilenceStatus(ctx, n.AlertIDs, hasSilenced, len(messages) != 0); err != nil { - return fmt.Errorf("failed updating silence status: %w", err) - } - } - - if len(messages) == 0 { - s.logger.Info("no messages to enqueue") - return nil - } - - if err := s.q.Enqueue(ctx, messages...); err != nil { - return fmt.Errorf("failed enqueuing messages: %w", err) - } - - return nil -} - -func (s *Service) dispatchEvents(ctx context.Context, n Notification) error { - if len(n.ReceiverSelectors) == 0 && len(n.Labels) == 0 { - return errors.ErrInvalid.WithMsgf("no receivers found") - } - - if len(n.ReceiverSelectors) != 0 { - if err := s.dispatchByFlow(ctx, n, FlowReceiver); err != nil { - return err - } - } - - if len(n.Labels) != 0 { - if err := s.dispatchByFlow(ctx, n, FlowSubscriber); err != nil { - return err + if err := s.deps.Repository.Rollback(ctx, err); err != nil { + return nil, err } + return nil, err } - return nil -} -func (s *Service) dispatchAlerts(ctx context.Context, n Notification) error { - if err := s.dispatchByFlow(ctx, n, FlowSubscriber); err != nil { - return err + if err := s.deps.Repository.Commit(ctx); err != nil { + return nil, err } - return nil + return ids, nil } func (s *Service) CheckIdempotency(ctx context.Context, scope, key string) (string, error) { - idempt, err := s.idempotencyRepository.Check(ctx, scope, key) + idempt, err := s.deps.IdempotencyRepository.Check(ctx, scope, key) if err != nil { return "", err } @@ -261,7 +111,7 @@ func (s *Service) CheckIdempotency(ctx context.Context, scope, key string) (stri } func (s *Service) InsertIdempotency(ctx context.Context, scope, key, notificationID string) error { - if _, err := s.idempotencyRepository.Create(ctx, scope, key, notificationID); err != nil { + if _, err := s.deps.IdempotencyRepository.Create(ctx, scope, key, notificationID); err != nil { return err } @@ -269,13 +119,13 @@ func (s *Service) InsertIdempotency(ctx context.Context, scope, key, notificatio } func (s *Service) RemoveIdempotencies(ctx context.Context, TTL time.Duration) error { - return s.idempotencyRepository.Delete(ctx, IdempotencyFilter{ + return s.deps.IdempotencyRepository.Delete(ctx, IdempotencyFilter{ TTL: TTL, }) } func (s *Service) ListNotificationMessages(ctx context.Context, notificationID string) ([]Message, error) { - messages, err := s.q.ListMessages(ctx, notificationID) + messages, err := s.deps.Q.ListMessages(ctx, notificationID) if err != nil { return nil, err } @@ -303,96 +153,8 @@ func (s *Service) discardSecrets(messages []Message) []Message { return newMessages } -// Transform alerts and populate Data and Labels to be interpolated to the system-default template -// .Data -// - id -// - status "FIRING"/"RESOLVED" -// - resource -// - template -// - metric_value -// - metric_name -// - generator_url -// - num_alerts_firing -// - dashboard -// - playbook -// - summary -// .Labels -// - severity "WARNING"/"CRITICAL" -// - alertname -// - (others labels defined in rules) -func (s *Service) BuildFromAlerts( - alerts []alert.Alert, - firingLen int, - createdTime time.Time, -) ([]Notification, error) { - if len(alerts) == 0 { - return nil, errors.New("empty alerts") - } - - alertsMap, err := groupByLabels(alerts, s.cfg.GroupBy) - if err != nil { - return nil, err - } - - var notifications []Notification - - for hashKey, groupedAlerts := range alertsMap { - sampleAlert := groupedAlerts[0] - - data := map[string]any{} - - mergedAnnotations := map[string][]string{} - for _, a := range groupedAlerts { - for k, v := range a.Annotations { - mergedAnnotations[k] = append(mergedAnnotations[k], v) - } - } - // make unique - for k, v := range mergedAnnotations { - mergedAnnotations[k] = removeDuplicateStringValues(v) - } - // render annotations - for k, vSlice := range mergedAnnotations { - for _, v := range vSlice { - if _, ok := data[k]; ok { - data[k] = fmt.Sprintf("%s\n%s", data[k], v) - } else { - data[k] = v - } - } - } - - data["status"] = sampleAlert.Status - data["generator_url"] = sampleAlert.GeneratorURL - data["num_alerts_firing"] = firingLen - - alertIDs := []int64{} - - for _, a := range groupedAlerts { - alertIDs = append(alertIDs, int64(a.ID)) - } - - for k, v := range sampleAlert.Labels { - data[k] = v - } - - notifications = append(notifications, Notification{ - NamespaceID: sampleAlert.NamespaceID, - Type: TypeAlert, - Data: data, - Labels: sampleAlert.Labels, - Template: template.ReservedName_SystemDefault, - UniqueKey: hashGroupKey(sampleAlert.GroupKey, hashKey), - CreatedAt: createdTime, - AlertIDs: alertIDs, - }) - } - - return notifications, nil -} - func (s *Service) List(ctx context.Context, flt Filter) ([]Notification, error) { - notifications, err := s.repository.List(ctx, flt) + notifications, err := s.deps.Repository.List(ctx, flt) if err != nil { return nil, err } diff --git a/core/notification/service_test.go b/core/notification/service_test.go index 4391fcfd..1283f916 100644 --- a/core/notification/service_test.go +++ b/core/notification/service_test.go @@ -3,38 +3,36 @@ package notification_test import ( "context" "testing" - "time" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" saltlog "github.com/goto/salt/log" - "github.com/goto/siren/core/alert" - "github.com/goto/siren/core/log" "github.com/goto/siren/core/notification" "github.com/goto/siren/core/notification/mocks" - "github.com/goto/siren/core/template" "github.com/goto/siren/pkg/errors" "github.com/stretchr/testify/mock" ) -const testPluginType = "test" +const ( + testType = "test" +) func TestService_DispatchFailure(t *testing.T) { tests := []struct { name string - n notification.Notification - setup func(notification.Notification, *mocks.Repository, *mocks.LogService, *mocks.AlertService, *mocks.Queuer, *mocks.Dispatcher) + n []notification.Notification + setup func([]notification.Notification, *mocks.Repository, *mocks.AlertRepository, *mocks.LogService, *mocks.AlertService, *mocks.Queuer, *mocks.Dispatcher) wantErr bool }{ { name: "should return error if repository return error", - n: notification.Notification{ - Type: notification.TypeAlert, - Labels: map[string]string{ - "k1": "v1", + n: []notification.Notification{ + { + Type: notification.TypeAlert, + Labels: map[string]string{ + "k1": "v1", + }, }, }, - setup: func(n notification.Notification, r *mocks.Repository, _ *mocks.LogService, _ *mocks.AlertService, _ *mocks.Queuer, _ *mocks.Dispatcher) { + setup: func(n []notification.Notification, r *mocks.Repository, _ *mocks.AlertRepository, _ *mocks.LogService, _ *mocks.AlertService, _ *mocks.Queuer, _ *mocks.Dispatcher) { r.EXPECT().Rollback(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("*errors.errorString")).Return(nil) r.EXPECT().Create(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(notification.Notification{}, errors.New("some error")) }, @@ -42,81 +40,91 @@ func TestService_DispatchFailure(t *testing.T) { }, { name: "should return error if dispatcher service return error", - n: notification.Notification{ - Type: notification.TypeAlert, - Labels: map[string]string{ - "k1": "v1", + n: []notification.Notification{ + { + Type: notification.TypeAlert, + Labels: map[string]string{ + "k1": "v1", + }, }, }, - setup: func(n notification.Notification, r *mocks.Repository, _ *mocks.LogService, _ *mocks.AlertService, _ *mocks.Queuer, d *mocks.Dispatcher) { + setup: func(n []notification.Notification, r *mocks.Repository, _ *mocks.AlertRepository, _ *mocks.LogService, _ *mocks.AlertService, _ *mocks.Queuer, d *mocks.Dispatcher) { r.EXPECT().Rollback(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("*errors.errorString")).Return(nil) r.EXPECT().Create(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(notification.Notification{}, nil) - d.EXPECT().PrepareMessage(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(nil, nil, false, errors.New("some error")) + d.EXPECT().Dispatch(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("[]notification.Notification")).Return(nil, errors.New("some error")) }, wantErr: true, }, { name: "should return error if dispatcher service return empty results", - n: notification.Notification{ - Type: notification.TypeAlert, - Labels: map[string]string{ - "k1": "v1", + n: []notification.Notification{ + { + Type: notification.TypeAlert, + Labels: map[string]string{ + "k1": "v1", + }, }, }, - setup: func(n notification.Notification, r *mocks.Repository, _ *mocks.LogService, _ *mocks.AlertService, _ *mocks.Queuer, d *mocks.Dispatcher) { + setup: func(n []notification.Notification, r *mocks.Repository, _ *mocks.AlertRepository, _ *mocks.LogService, _ *mocks.AlertService, _ *mocks.Queuer, d *mocks.Dispatcher) { r.EXPECT().Rollback(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("*errors.errorString")).Return(nil) r.EXPECT().Create(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(notification.Notification{}, nil) - d.EXPECT().PrepareMessage(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(nil, nil, false, nil) + d.EXPECT().Dispatch(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(nil, nil) }, wantErr: true, }, { name: "should return error if log notifications return error", - n: notification.Notification{ - Type: notification.TypeAlert, - Labels: map[string]string{ - "k1": "v1", + n: []notification.Notification{ + { + Type: notification.TypeAlert, + Labels: map[string]string{ + "k1": "v1", + }, }, }, - setup: func(n notification.Notification, r *mocks.Repository, l *mocks.LogService, _ *mocks.AlertService, _ *mocks.Queuer, d *mocks.Dispatcher) { + setup: func(n []notification.Notification, r *mocks.Repository, _ *mocks.AlertRepository, l *mocks.LogService, _ *mocks.AlertService, _ *mocks.Queuer, d *mocks.Dispatcher) { r.EXPECT().Rollback(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("*fmt.wrapError")).Return(nil) r.EXPECT().Create(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(notification.Notification{}, nil) - d.EXPECT().PrepareMessage(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return([]notification.Message{{ID: "123"}}, []log.Notification{{ReceiverID: 123}}, false, nil) + d.EXPECT().Dispatch(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return([]string{"123"}, nil) l.EXPECT().LogNotifications(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("log.Notification")).Return(errors.New("some error")) }, wantErr: true, }, { name: "should return error if update alerts silence status return error", - n: notification.Notification{ - Type: notification.TypeAlert, - Labels: map[string]string{ - "k1": "v1", + n: []notification.Notification{ + { + Type: notification.TypeAlert, + Labels: map[string]string{ + "k1": "v1", + }, }, }, - setup: func(n notification.Notification, r *mocks.Repository, l *mocks.LogService, a *mocks.AlertService, _ *mocks.Queuer, d *mocks.Dispatcher) { + setup: func(n []notification.Notification, r *mocks.Repository, ar *mocks.AlertRepository, l *mocks.LogService, a *mocks.AlertService, _ *mocks.Queuer, d *mocks.Dispatcher) { r.EXPECT().Rollback(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("*fmt.wrapError")).Return(nil) r.EXPECT().Create(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(notification.Notification{}, nil) - d.EXPECT().PrepareMessage(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return([]notification.Message{{ID: "123"}}, []log.Notification{{ReceiverID: 123}}, false, nil) + d.EXPECT().Dispatch(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("[]notification.Notification")).Return([]string{"123"}, nil) l.EXPECT().LogNotifications(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("log.Notification")).Return(nil) - a.EXPECT().UpdateSilenceStatus(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("[]int64"), mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(errors.New("some error")) + ar.EXPECT().BulkUpdateSilence(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("[]int64"), mock.AnythingOfType("string")).Return(errors.New("some error")) }, wantErr: true, }, { name: "should return error if enqueue return error", - n: notification.Notification{ - Type: notification.TypeAlert, - Labels: map[string]string{ - "k1": "v1", + n: []notification.Notification{ + { + Type: notification.TypeAlert, + Labels: map[string]string{ + "k1": "v1", + }, }, }, - setup: func(n notification.Notification, r *mocks.Repository, l *mocks.LogService, a *mocks.AlertService, q *mocks.Queuer, d *mocks.Dispatcher) { + setup: func(n []notification.Notification, r *mocks.Repository, ar *mocks.AlertRepository, l *mocks.LogService, a *mocks.AlertService, q *mocks.Queuer, d *mocks.Dispatcher) { r.EXPECT().Rollback(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("*fmt.wrapError")).Return(nil) r.EXPECT().Create(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(notification.Notification{}, nil) - d.EXPECT().PrepareMessage(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return([]notification.Message{{ID: "123"}}, []log.Notification{{ReceiverID: 123}}, false, nil) + d.EXPECT().Dispatch(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return([]string{"123"}, nil) l.EXPECT().LogNotifications(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("log.Notification")).Return(nil) - a.EXPECT().UpdateSilenceStatus(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("[]int64"), mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(nil) + ar.EXPECT().BulkUpdateSilence(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("[]int64"), mock.AnythingOfType("string")).Return(errors.New("some error")) q.EXPECT().Enqueue(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Message")).Return(errors.New("some error")) }, wantErr: true, @@ -125,33 +133,35 @@ func TestService_DispatchFailure(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var ( - mockQueuer = new(mocks.Queuer) - mockRepository = new(mocks.Repository) - mockDispatcher = new(mocks.Dispatcher) - mockLogService = new(mocks.LogService) - mockAlertService = new(mocks.AlertService) + mockQueuer = new(mocks.Queuer) + mockRepository = new(mocks.Repository) + mockAlertRepository = new(mocks.AlertRepository) + mockLogService = new(mocks.LogService) + mockAlertService = new(mocks.AlertService) + mockDispatcher = new(mocks.Dispatcher) ) if tt.setup != nil { mockRepository.EXPECT().WithTransaction(mock.AnythingOfType("context.todoCtx")).Return(context.TODO()) - tt.setup(tt.n, mockRepository, mockLogService, mockAlertService, mockQueuer, mockDispatcher) + tt.setup(tt.n, mockRepository, mockAlertRepository, mockLogService, mockAlertService, mockQueuer, mockDispatcher) } s := notification.NewService( - saltlog.NewNoop(), - notification.Config{}, - mockRepository, - mockQueuer, - nil, notification.Deps{ - AlertService: mockAlertService, - LogService: mockLogService, - DispatchReceiverService: mockDispatcher, - DispatchSubscriberService: mockDispatcher, + Cfg: notification.Config{ + EnableSilenceFeature: true, + }, + Logger: saltlog.NewNoop(), + Repository: mockRepository, + Q: mockQueuer, + AlertRepository: mockAlertRepository, + LogService: mockLogService, + }, + map[string]notification.Dispatcher{ + testType: mockDispatcher, }, - true, ) - if _, err := s.Dispatch(context.TODO(), tt.n); (err != nil) != tt.wantErr { + if _, err := s.Dispatch(context.TODO(), tt.n, notification.DispatchKindSingleNotification); (err != nil) != tt.wantErr { t.Errorf("Service.DispatchFailure() error = %v, wantErr %v", err, tt.wantErr) } }) @@ -161,23 +171,25 @@ func TestService_DispatchFailure(t *testing.T) { func TestService_DispatchSuccess(t *testing.T) { tests := []struct { name string - n notification.Notification - setup func(notification.Notification, *mocks.Repository, *mocks.LogService, *mocks.AlertService, *mocks.Queuer, *mocks.Dispatcher) + n []notification.Notification + setup func([]notification.Notification, *mocks.Repository, *mocks.AlertRepository, *mocks.LogService, *mocks.AlertService, *mocks.Queuer, *mocks.Dispatcher) wantErr bool }{ { name: "should return no error if enqueue success", - n: notification.Notification{ - Type: notification.TypeAlert, - Labels: map[string]string{ - "k1": "v1", + n: []notification.Notification{ + { + Type: notification.TypeAlert, + Labels: map[string]string{ + "k1": "v1", + }, }, }, - setup: func(n notification.Notification, r *mocks.Repository, l *mocks.LogService, a *mocks.AlertService, q *mocks.Queuer, d *mocks.Dispatcher) { - r.EXPECT().Create(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(n, nil) - d.EXPECT().PrepareMessage(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return([]notification.Message{{ID: "123"}}, []log.Notification{{ReceiverID: 123}}, false, nil) + setup: func(n []notification.Notification, r *mocks.Repository, ar *mocks.AlertRepository, l *mocks.LogService, a *mocks.AlertService, q *mocks.Queuer, d *mocks.Dispatcher) { + r.EXPECT().Create(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return(n[0], nil) + d.EXPECT().Dispatch(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("[]notification.Notification")).Return([]string{"123"}, nil) l.EXPECT().LogNotifications(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("log.Notification")).Return(nil) - a.EXPECT().UpdateSilenceStatus(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("[]int64"), mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(nil) + ar.EXPECT().BulkUpdateSilence(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("[]int64"), mock.AnythingOfType("string")).Return(nil) q.EXPECT().Enqueue(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Message")).Return(nil) }, }, @@ -185,157 +197,37 @@ func TestService_DispatchSuccess(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var ( - mockQueuer = new(mocks.Queuer) - mockRepository = new(mocks.Repository) - mockDispatcher = new(mocks.Dispatcher) - mockLogService = new(mocks.LogService) - mockAlertService = new(mocks.AlertService) + mockQueuer = new(mocks.Queuer) + mockRepository = new(mocks.Repository) + mockAlertRepository = new(mocks.AlertRepository) + mockLogService = new(mocks.LogService) + mockAlertService = new(mocks.AlertService) + mockDispatcher = new(mocks.Dispatcher) ) if tt.setup != nil { mockRepository.EXPECT().WithTransaction(mock.AnythingOfType("context.todoCtx")).Return(context.TODO()) mockRepository.EXPECT().Commit(mock.AnythingOfType("context.todoCtx")).Return(nil) - tt.setup(tt.n, mockRepository, mockLogService, mockAlertService, mockQueuer, mockDispatcher) + tt.setup(tt.n, mockRepository, mockAlertRepository, mockLogService, mockAlertService, mockQueuer, mockDispatcher) } s := notification.NewService( - saltlog.NewNoop(), - notification.Config{}, - mockRepository, - mockQueuer, - nil, notification.Deps{ - AlertService: mockAlertService, - LogService: mockLogService, - DispatchReceiverService: mockDispatcher, - DispatchSubscriberService: mockDispatcher, - }, - true, - ) - if _, err := s.Dispatch(context.TODO(), tt.n); (err != nil) != tt.wantErr { - t.Errorf("Service.Dispatch() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestService_BuildFromAlerts(t *testing.T) { - tests := []struct { - name string - alerts []alert.Alert - firingLen int - want []notification.Notification - errString string - }{ - - { - name: "should return empty notification if alerts slice is empty", - errString: "empty alerts", - }, - { - name: "should properly return notification (same annotations are joined by newline and different labels are splitted into two notifications)", - alerts: []alert.Alert{ - { - ID: 14, - ProviderID: 1, - NamespaceID: 1, - ResourceName: "test-alert-host-1", - MetricName: "test-alert", - MetricValue: "15", - Severity: "WARNING", - Rule: "test-alert-template", - Labels: map[string]string{"lk1": "lv1"}, - Annotations: map[string]string{"ak1": "akv1"}, - Status: "FIRING", - }, - { - ID: 15, - ProviderID: 1, - NamespaceID: 1, - ResourceName: "test-alert-host-2", - MetricName: "test-alert", - MetricValue: "16", - Severity: "WARNING", - Rule: "test-alert-template", - Labels: map[string]string{"lk1": "lv1", "lk2": "lv2"}, - Annotations: map[string]string{"ak1": "akv1"}, - Status: "FIRING", - }, - { - ID: 16, - ProviderID: 1, - NamespaceID: 1, - ResourceName: "test-alert-host-2", - MetricName: "test-alert", - MetricValue: "16", - Severity: "WARNING", - Rule: "test-alert-template", - Labels: map[string]string{"lk1": "lv1", "lk2": "lv2"}, - Annotations: map[string]string{"ak1": "akv11", "ak2": "akv2"}, - Status: "FIRING", - }, - }, - firingLen: 2, - want: []notification.Notification{ - { - NamespaceID: 1, - Type: notification.TypeAlert, - Data: map[string]any{ - "generator_url": "", - "num_alerts_firing": 2, - "status": "FIRING", - "ak1": "akv1", - "lk1": "lv1", + Cfg: notification.Config{ + EnableSilenceFeature: true, }, - Labels: map[string]string{ - "lk1": "lv1", - }, - UniqueKey: "ignored", - Template: template.ReservedName_SystemDefault, - AlertIDs: []int64{14}, + Logger: saltlog.NewNoop(), + Repository: mockRepository, + Q: mockQueuer, + AlertRepository: mockAlertRepository, + LogService: mockLogService, }, - { - NamespaceID: 1, - Type: notification.TypeAlert, - - Data: map[string]any{ - "generator_url": "", - "num_alerts_firing": 2, - "status": "FIRING", - "ak1": "akv1\nakv11", - "ak2": "akv2", - "lk1": "lv1", - "lk2": "lv2", - }, - Labels: map[string]string{ - "lk1": "lv1", - "lk2": "lv2", - }, - UniqueKey: "ignored", - Template: template.ReservedName_SystemDefault, - AlertIDs: []int64{15, 16}, + map[string]notification.Dispatcher{ + testType: mockDispatcher, }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := notification.NewService( - saltlog.NewNoop(), - notification.Config{}, - nil, - nil, - nil, - notification.Deps{}, - false, ) - got, err := s.BuildFromAlerts(tt.alerts, tt.firingLen, time.Time{}) - if (err != nil) && (err.Error() != tt.errString) { - t.Errorf("BuildTypeReceiver() error = %v, wantErr %s", err, tt.errString) - return - } - if diff := cmp.Diff(got, tt.want, cmpopts.IgnoreFields(notification.Notification{}, "ID", "UniqueKey"), cmpopts.SortSlices(func(a, b notification.Notification) bool { return len(a.Labels) < len(b.Labels) })); diff != "" { - t.Errorf("BuildFromAlerts() got diff = %v", diff) + if _, err := s.Dispatch(context.TODO(), tt.n, testType); (err != nil) != tt.wantErr { + t.Errorf("Service.Dispatch() error = %v, wantErr %v", err, tt.wantErr) } }) } diff --git a/core/notification/utils.go b/core/notification/utils.go deleted file mode 100644 index 5fc838f8..00000000 --- a/core/notification/utils.go +++ /dev/null @@ -1,61 +0,0 @@ -package notification - -import ( - "crypto/sha256" - "fmt" - - "github.com/goto/siren/core/alert" - "github.com/mitchellh/hashstructure/v2" -) - -func removeDuplicateStringValues(strSlice []string) []string { - keys := make(map[string]bool) - list := []string{} - - for _, v := range strSlice { - if _, value := keys[v]; !value { - keys[v] = true - list = append(list, v) - } - } - return list -} - -func groupByLabels(alerts []alert.Alert, groupBy []string) (map[uint64][]alert.Alert, error) { - var alertsMap = map[uint64][]alert.Alert{} - - for _, a := range alerts { - var groupLabels = buildGroupLabels(a.Labels, groupBy) - if len(groupLabels) == 0 { - groupLabels = a.Labels - } - hash, err := hashstructure.Hash(groupLabels, hashstructure.FormatV2, nil) - if err != nil { - return nil, fmt.Errorf("cannot get hash from alert %v", a) - } - alertsMap[hash] = append(alertsMap[hash], a) - } - - return alertsMap, nil -} - -func buildGroupLabels(alertLabels map[string]string, groupBy []string) map[string]string { - var groupLabels = map[string]string{} - - for _, g := range groupBy { - if v, ok := alertLabels[g]; ok { - groupLabels[g] = v - } - } - - return groupLabels -} - -// hashGroupKey hash groupKey from alert and hashKey from labels -func hashGroupKey(groupKey string, hashKey uint64) string { - h := sha256.New() - // hash.Hash.Write never returns an error. - //nolint: errcheck - h.Write([]byte(fmt.Sprintf("%s%d", groupKey, hashKey))) - return fmt.Sprintf("%x", h.Sum(nil)) -} diff --git a/core/notification/v1beta1.go b/core/notification/v1beta1.go index a0e76284..b3697bee 100644 --- a/core/notification/v1beta1.go +++ b/core/notification/v1beta1.go @@ -19,18 +19,18 @@ func (m *Message) ToV1beta1Proto() (*sirenv1beta1.NotificationMessage, error) { } return &sirenv1beta1.NotificationMessage{ - Id: m.ID, - NotificationId: m.NotificationID, - Status: m.Status.String(), - ReceiverType: m.ReceiverType, - Configs: configs, - Details: details, - LastError: m.LastError, - MaxTries: uint64(m.MaxTries), - TryCount: uint64(m.TryCount), - Retryable: m.Retryable, - ExpiredAt: timestamppb.New(m.ExpiredAt), - CreatedAt: timestamppb.New(m.CreatedAt), - UpdatedAt: timestamppb.New(m.UpdatedAt), + Id: m.ID, + NotificationIds: m.NotificationIDs, + Status: m.Status.String(), + ReceiverType: m.ReceiverType, + Configs: configs, + Details: details, + LastError: m.LastError, + MaxTries: uint64(m.MaxTries), + TryCount: uint64(m.TryCount), + Retryable: m.Retryable, + ExpiredAt: timestamppb.New(m.ExpiredAt), + CreatedAt: timestamppb.New(m.CreatedAt), + UpdatedAt: timestamppb.New(m.UpdatedAt), }, nil } diff --git a/core/silence/status.go b/core/silence/status.go new file mode 100644 index 00000000..778fb8f2 --- /dev/null +++ b/core/silence/status.go @@ -0,0 +1,15 @@ +package silence + +const ( + StatusTotal = "total" + StatusPartial = "partial" +) + +func Status(hasSilenced, hasNonSilenced bool) string { + if hasSilenced && !hasNonSilenced { + return StatusTotal + } else if hasSilenced && hasNonSilenced { + return StatusPartial + } + return "" +} diff --git a/core/subscription/subscription.go b/core/subscription/subscription.go index fdcb7e26..4415cbaf 100644 --- a/core/subscription/subscription.go +++ b/core/subscription/subscription.go @@ -5,6 +5,7 @@ import ( "fmt" "time" + "github.com/goto/siren/core/receiver" "github.com/goto/siren/core/silence" ) @@ -37,6 +38,18 @@ type ReceiverView struct { Match map[string]string `json:"match"` } +func (rcv *ReceiverView) FromReceiver(r receiver.Receiver) { + rcv.ID = r.ID + rcv.Name = r.Name + rcv.Labels = r.Labels + rcv.Type = r.Type + rcv.Configurations = r.Configurations + rcv.ParentID = r.ParentID + rcv.CreatedAt = r.CreatedAt + rcv.UpdatedAt = r.UpdatedAt + +} + type Receiver struct { ID uint64 `json:"id"` Configuration map[string]any `json:"configuration"` diff --git a/core/template/mapper.go b/core/template/mapper.go index 8570f798..74af494f 100644 --- a/core/template/mapper.go +++ b/core/template/mapper.go @@ -2,6 +2,7 @@ package template import ( "regexp" + "strconv" "strings" texttemplate "text/template" @@ -37,6 +38,18 @@ var defaultFuncMap = func() texttemplate.FuncMap { "stringSlice": func(s ...string) []string { return s }, + "sub": func(a, b string) string { + aint, err := strconv.ParseInt(a, 10, 64) + if err != nil { + return "unknown" // fallback + } + bint, err := strconv.ParseInt(b, 10, 64) + if err != nil { + return "unknown" // fallback + } + + return strconv.FormatInt(aint-bint, 10) + }, } for k, v := range extra { diff --git a/core/template/service.go b/core/template/service.go index 24dc6227..a0fc71e3 100644 --- a/core/template/service.go +++ b/core/template/service.go @@ -87,7 +87,7 @@ func RenderWithEnrichedDefault(templateBody string, templateVars []Variable, req return RenderBody(templateBody, enrichedVariables, defaultLeftDelim, defaultrightDelim) } -func RenderBody(templateBody string, aStruct interface{}, leftDelim, rightDelim string) (string, error) { +func RenderBody(templateBody string, aStruct any, leftDelim, rightDelim string) (string, error) { var tpl bytes.Buffer tmpl, err := texttemplate.New("parser").Funcs(defaultFuncMap).Delims(leftDelim, rightDelim).Parse(templateBody) if err != nil { diff --git a/go.mod b/go.mod index 4e9ce83a..a7e14a64 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,8 @@ require ( github.com/spf13/cobra v1.8.0 github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.26.0 - golang.org/x/text v0.14.0 + golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d + golang.org/x/text v0.15.0 google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac // indirect google.golang.org/grpc v1.61.0 google.golang.org/protobuf v1.32.0 @@ -42,7 +43,8 @@ require ( go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 go.opentelemetry.io/otel v1.24.0 - golang.org/x/sync v0.6.0 + go.opentelemetry.io/otel/metric v1.24.0 + golang.org/x/sync v0.7.0 google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe gopkg.in/yaml.v2 v2.4.0 ) @@ -194,7 +196,6 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.45.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 // indirect - go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/sdk v1.24.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect @@ -202,13 +203,13 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/goleak v1.3.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.22.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.25.0 // indirect golang.org/x/oauth2 v0.16.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/term v0.18.0 // indirect - golang.org/x/tools v0.17.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/term v0.20.0 // indirect + golang.org/x/tools v0.21.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index af75386d..e8532ad5 100644 --- a/go.sum +++ b/go.sum @@ -3058,8 +3058,8 @@ golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -3079,6 +3079,8 @@ golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8H golang.org/x/exp v0.0.0-20200821190819-94841d0725da/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d h1:N0hmiNbwsSNwHBAvR3QB5w25pUwH4tK0Y/RltD1j1h4= +golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -3124,8 +3126,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -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/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -3231,8 +3233,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -3293,8 +3295,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -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/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -3466,8 +3468,8 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= @@ -3482,8 +3484,8 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -3505,8 +3507,8 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -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/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -3626,8 +3628,8 @@ golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= +golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/go.work.sum b/go.work.sum index f67a85f0..52f01da5 100644 --- a/go.work.sum +++ b/go.work.sum @@ -235,23 +235,32 @@ golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= google.golang.org/api v0.128.0 h1:RjPESny5CnQRn9V6siglged+DZCgfu9l6mO9dkX9VOg= google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI= google.golang.org/genproto v0.0.0-20230221151758-ace64dc21148/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= diff --git a/internal/api/api.go b/internal/api/api.go index a52264c0..54c2fce4 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -16,7 +16,7 @@ import ( ) type AlertService interface { - CreateAlerts(ctx context.Context, providerType string, providerID uint64, namespaceID uint64, body map[string]any) ([]alert.Alert, int, error) + CreateAlerts(ctx context.Context, providerType string, providerID uint64, namespaceID uint64, body map[string]any) ([]alert.Alert, error) List(context.Context, alert.Filter) ([]alert.Alert, error) } @@ -72,9 +72,8 @@ type TemplateService interface { } type NotificationService interface { - Dispatch(ctx context.Context, n notification.Notification) (string, error) + Dispatch(context.Context, []notification.Notification, string) ([]string, error) RemoveIdempotencies(ctx context.Context, TTL time.Duration) error - BuildFromAlerts(alerts []alert.Alert, firingLen int, createdTime time.Time) ([]notification.Notification, error) CheckIdempotency(ctx context.Context, scope, key string) (string, error) InsertIdempotency(ctx context.Context, scope, key, notificationID string) error ListNotificationMessages(ctx context.Context, notificationID string) ([]notification.Message, error) diff --git a/internal/api/mocks/alert_service.go b/internal/api/mocks/alert_service.go index 6b16836d..0363faa0 100644 --- a/internal/api/mocks/alert_service.go +++ b/internal/api/mocks/alert_service.go @@ -24,7 +24,7 @@ func (_m *AlertService) EXPECT() *AlertService_Expecter { } // CreateAlerts provides a mock function with given fields: ctx, providerType, providerID, namespaceID, body -func (_m *AlertService) CreateAlerts(ctx context.Context, providerType string, providerID uint64, namespaceID uint64, body map[string]interface{}) ([]alert.Alert, int, error) { +func (_m *AlertService) CreateAlerts(ctx context.Context, providerType string, providerID uint64, namespaceID uint64, body map[string]interface{}) ([]alert.Alert, error) { ret := _m.Called(ctx, providerType, providerID, namespaceID, body) if len(ret) == 0 { @@ -32,9 +32,8 @@ func (_m *AlertService) CreateAlerts(ctx context.Context, providerType string, p } var r0 []alert.Alert - var r1 int - var r2 error - if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64, map[string]interface{}) ([]alert.Alert, int, error)); ok { + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64, map[string]interface{}) ([]alert.Alert, error)); ok { return rf(ctx, providerType, providerID, namespaceID, body) } if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64, map[string]interface{}) []alert.Alert); ok { @@ -45,19 +44,13 @@ func (_m *AlertService) CreateAlerts(ctx context.Context, providerType string, p } } - if rf, ok := ret.Get(1).(func(context.Context, string, uint64, uint64, map[string]interface{}) int); ok { + if rf, ok := ret.Get(1).(func(context.Context, string, uint64, uint64, map[string]interface{}) error); ok { r1 = rf(ctx, providerType, providerID, namespaceID, body) } else { - r1 = ret.Get(1).(int) - } - - if rf, ok := ret.Get(2).(func(context.Context, string, uint64, uint64, map[string]interface{}) error); ok { - r2 = rf(ctx, providerType, providerID, namespaceID, body) - } else { - r2 = ret.Error(2) + r1 = ret.Error(1) } - return r0, r1, r2 + return r0, r1 } // AlertService_CreateAlerts_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateAlerts' @@ -82,12 +75,12 @@ func (_c *AlertService_CreateAlerts_Call) Run(run func(ctx context.Context, prov return _c } -func (_c *AlertService_CreateAlerts_Call) Return(_a0 []alert.Alert, _a1 int, _a2 error) *AlertService_CreateAlerts_Call { - _c.Call.Return(_a0, _a1, _a2) +func (_c *AlertService_CreateAlerts_Call) Return(_a0 []alert.Alert, _a1 error) *AlertService_CreateAlerts_Call { + _c.Call.Return(_a0, _a1) return _c } -func (_c *AlertService_CreateAlerts_Call) RunAndReturn(run func(context.Context, string, uint64, uint64, map[string]interface{}) ([]alert.Alert, int, error)) *AlertService_CreateAlerts_Call { +func (_c *AlertService_CreateAlerts_Call) RunAndReturn(run func(context.Context, string, uint64, uint64, map[string]interface{}) ([]alert.Alert, error)) *AlertService_CreateAlerts_Call { _c.Call.Return(run) return _c } diff --git a/internal/api/mocks/notification_service.go b/internal/api/mocks/notification_service.go index a234f68d..2a5941cd 100644 --- a/internal/api/mocks/notification_service.go +++ b/internal/api/mocks/notification_service.go @@ -3,13 +3,10 @@ package mocks import ( - alert "github.com/goto/siren/core/alert" - context "context" - mock "github.com/stretchr/testify/mock" - notification "github.com/goto/siren/core/notification" + mock "github.com/stretchr/testify/mock" time "time" ) @@ -27,66 +24,6 @@ func (_m *NotificationService) EXPECT() *NotificationService_Expecter { return &NotificationService_Expecter{mock: &_m.Mock} } -// BuildFromAlerts provides a mock function with given fields: alerts, firingLen, createdTime -func (_m *NotificationService) BuildFromAlerts(alerts []alert.Alert, firingLen int, createdTime time.Time) ([]notification.Notification, error) { - ret := _m.Called(alerts, firingLen, createdTime) - - if len(ret) == 0 { - panic("no return value specified for BuildFromAlerts") - } - - var r0 []notification.Notification - var r1 error - if rf, ok := ret.Get(0).(func([]alert.Alert, int, time.Time) ([]notification.Notification, error)); ok { - return rf(alerts, firingLen, createdTime) - } - if rf, ok := ret.Get(0).(func([]alert.Alert, int, time.Time) []notification.Notification); ok { - r0 = rf(alerts, firingLen, createdTime) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]notification.Notification) - } - } - - if rf, ok := ret.Get(1).(func([]alert.Alert, int, time.Time) error); ok { - r1 = rf(alerts, firingLen, createdTime) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// NotificationService_BuildFromAlerts_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BuildFromAlerts' -type NotificationService_BuildFromAlerts_Call struct { - *mock.Call -} - -// BuildFromAlerts is a helper method to define mock.On call -// - alerts []alert.Alert -// - firingLen int -// - createdTime time.Time -func (_e *NotificationService_Expecter) BuildFromAlerts(alerts interface{}, firingLen interface{}, createdTime interface{}) *NotificationService_BuildFromAlerts_Call { - return &NotificationService_BuildFromAlerts_Call{Call: _e.mock.On("BuildFromAlerts", alerts, firingLen, createdTime)} -} - -func (_c *NotificationService_BuildFromAlerts_Call) Run(run func(alerts []alert.Alert, firingLen int, createdTime time.Time)) *NotificationService_BuildFromAlerts_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].([]alert.Alert), args[1].(int), args[2].(time.Time)) - }) - return _c -} - -func (_c *NotificationService_BuildFromAlerts_Call) Return(_a0 []notification.Notification, _a1 error) *NotificationService_BuildFromAlerts_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *NotificationService_BuildFromAlerts_Call) RunAndReturn(run func([]alert.Alert, int, time.Time) ([]notification.Notification, error)) *NotificationService_BuildFromAlerts_Call { - _c.Call.Return(run) - return _c -} - // CheckIdempotency provides a mock function with given fields: ctx, scope, key func (_m *NotificationService) CheckIdempotency(ctx context.Context, scope string, key string) (string, error) { ret := _m.Called(ctx, scope, key) @@ -145,27 +82,29 @@ func (_c *NotificationService_CheckIdempotency_Call) RunAndReturn(run func(conte return _c } -// Dispatch provides a mock function with given fields: ctx, n -func (_m *NotificationService) Dispatch(ctx context.Context, n notification.Notification) (string, error) { - ret := _m.Called(ctx, n) +// Dispatch provides a mock function with given fields: _a0, _a1, _a2 +func (_m *NotificationService) Dispatch(_a0 context.Context, _a1 []notification.Notification, _a2 string) ([]string, error) { + ret := _m.Called(_a0, _a1, _a2) if len(ret) == 0 { panic("no return value specified for Dispatch") } - var r0 string + var r0 []string var r1 error - if rf, ok := ret.Get(0).(func(context.Context, notification.Notification) (string, error)); ok { - return rf(ctx, n) + if rf, ok := ret.Get(0).(func(context.Context, []notification.Notification, string) ([]string, error)); ok { + return rf(_a0, _a1, _a2) } - if rf, ok := ret.Get(0).(func(context.Context, notification.Notification) string); ok { - r0 = rf(ctx, n) + if rf, ok := ret.Get(0).(func(context.Context, []notification.Notification, string) []string); ok { + r0 = rf(_a0, _a1, _a2) } else { - r0 = ret.Get(0).(string) + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } } - if rf, ok := ret.Get(1).(func(context.Context, notification.Notification) error); ok { - r1 = rf(ctx, n) + if rf, ok := ret.Get(1).(func(context.Context, []notification.Notification, string) error); ok { + r1 = rf(_a0, _a1, _a2) } else { r1 = ret.Error(1) } @@ -179,25 +118,26 @@ type NotificationService_Dispatch_Call struct { } // Dispatch is a helper method to define mock.On call -// - ctx context.Context -// - n notification.Notification -func (_e *NotificationService_Expecter) Dispatch(ctx interface{}, n interface{}) *NotificationService_Dispatch_Call { - return &NotificationService_Dispatch_Call{Call: _e.mock.On("Dispatch", ctx, n)} +// - _a0 context.Context +// - _a1 []notification.Notification +// - _a2 string +func (_e *NotificationService_Expecter) Dispatch(_a0 interface{}, _a1 interface{}, _a2 interface{}) *NotificationService_Dispatch_Call { + return &NotificationService_Dispatch_Call{Call: _e.mock.On("Dispatch", _a0, _a1, _a2)} } -func (_c *NotificationService_Dispatch_Call) Run(run func(ctx context.Context, n notification.Notification)) *NotificationService_Dispatch_Call { +func (_c *NotificationService_Dispatch_Call) Run(run func(_a0 context.Context, _a1 []notification.Notification, _a2 string)) *NotificationService_Dispatch_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(notification.Notification)) + run(args[0].(context.Context), args[1].([]notification.Notification), args[2].(string)) }) return _c } -func (_c *NotificationService_Dispatch_Call) Return(_a0 string, _a1 error) *NotificationService_Dispatch_Call { +func (_c *NotificationService_Dispatch_Call) Return(_a0 []string, _a1 error) *NotificationService_Dispatch_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *NotificationService_Dispatch_Call) RunAndReturn(run func(context.Context, notification.Notification) (string, error)) *NotificationService_Dispatch_Call { +func (_c *NotificationService_Dispatch_Call) RunAndReturn(run func(context.Context, []notification.Notification, string) ([]string, error)) *NotificationService_Dispatch_Call { _c.Call.Return(run) return _c } diff --git a/internal/api/v1beta1/alert.go b/internal/api/v1beta1/alert.go index 9dfa92fe..f11624db 100644 --- a/internal/api/v1beta1/alert.go +++ b/internal/api/v1beta1/alert.go @@ -2,7 +2,6 @@ package v1beta1 import ( "context" - "time" "github.com/goto/siren/core/alert" sirenv1beta1 "github.com/goto/siren/proto/gotocompany/siren/v1beta1" @@ -76,7 +75,7 @@ func (s *GRPCServer) CreateAlertsWithNamespace(ctx context.Context, req *sirenv1 } func (s *GRPCServer) createAlerts(ctx context.Context, providerType string, providerID uint64, namespaceID uint64, body map[string]any) ([]*sirenv1beta1.Alert, error) { - createdAlerts, firingLen, err := s.alertService.CreateAlerts(ctx, providerType, providerID, namespaceID, body) + createdAlerts, err := s.alertService.CreateAlerts(ctx, providerType, providerID, namespaceID, body) if err != nil { return nil, err } @@ -96,21 +95,5 @@ func (s *GRPCServer) createAlerts(ctx context.Context, providerType string, prov items = append(items, alertHistoryItem) } - if len(createdAlerts) > 0 { - // Publish to notification service - ns, err := s.notificationService.BuildFromAlerts(createdAlerts, firingLen, time.Now()) - if err != nil { - s.logger.Warn("failed to build notifications from alert", "err", err, "alerts", createdAlerts) - } - - for _, n := range ns { - if _, err := s.notificationService.Dispatch(ctx, n); err != nil { - s.logger.Warn("failed to send alert as notification", "err", err, "notification", n) - } - } - } else { - s.logger.Warn("failed to send alert as notification, empty created alerts") - } - return items, nil } diff --git a/internal/api/v1beta1/alert_test.go b/internal/api/v1beta1/alert_test.go index 1afa4ad7..e41f94bd 100644 --- a/internal/api/v1beta1/alert_test.go +++ b/internal/api/v1beta1/alert_test.go @@ -7,7 +7,6 @@ import ( "github.com/goto/salt/log" "github.com/goto/siren/core/alert" - "github.com/goto/siren/core/notification" "github.com/goto/siren/internal/api" "github.com/goto/siren/internal/api/mocks" "github.com/goto/siren/internal/api/v1beta1" @@ -171,9 +170,8 @@ func TestGRPCServer_CreateAlertHistory(t *testing.T) { TriggeredAt: timenow, }} mockedAlertService.EXPECT().CreateAlerts(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("string"), mock.AnythingOfType("uint64"), mock.AnythingOfType("uint64"), payload). - Return(dummyAlerts, 1, nil).Once() - mockNotificationService.EXPECT().BuildFromAlerts(mock.AnythingOfType("[]alert.Alert"), mock.AnythingOfType("int"), mock.AnythingOfType("time.Time")).Return([]notification.Notification{}, nil) - mockNotificationService.EXPECT().Dispatch(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return("", nil) + Return(dummyAlerts, nil).Once() + mockNotificationService.EXPECT().Dispatch(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("[]notification.Notification"), mock.AnythingOfType("string")).Return(nil, nil) dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{AlertService: mockedAlertService, NotificationService: mockNotificationService}) @@ -280,9 +278,8 @@ func TestGRPCServer_CreateAlertHistory(t *testing.T) { TriggeredAt: timenow, }} mockedAlertService.EXPECT().CreateAlerts(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("string"), mock.AnythingOfType("uint64"), mock.AnythingOfType("uint64"), payload). - Return(dummyAlerts, 1, nil).Once() - mockNotificationService.EXPECT().BuildFromAlerts(mock.AnythingOfType("[]alert.Alert"), mock.AnythingOfType("int"), mock.AnythingOfType("time.Time")).Return([]notification.Notification{}, nil) - mockNotificationService.EXPECT().Dispatch(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return("", nil) + Return(dummyAlerts, nil).Once() + mockNotificationService.EXPECT().Dispatch(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("[]notification.Notification"), mock.AnythingOfType("string")).Return(nil, nil) dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{AlertService: mockedAlertService, NotificationService: mockNotificationService}) @@ -303,7 +300,7 @@ func TestGRPCServer_CreateAlertHistory(t *testing.T) { dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{AlertService: mockedAlertService}) mockedAlertService.EXPECT().CreateAlerts(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("string"), mock.AnythingOfType("uint64"), mock.AnythingOfType("uint64"), payload). - Return(nil, 0, errors.New("random error")).Once() + Return(nil, errors.New("random error")).Once() res, err := dummyGRPCServer.CreateAlerts(context.TODO(), dummyReq) assert.EqualError(t, err, "rpc error: code = Internal desc = some unexpected error occurred") @@ -462,9 +459,8 @@ func TestGRPCServer_CreateAlertHistory(t *testing.T) { dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{AlertService: mockedAlertService, NotificationService: mockNotificationService}) mockedAlertService.EXPECT().CreateAlerts(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("string"), mock.AnythingOfType("uint64"), mock.AnythingOfType("uint64"), payload). - Return(dummyAlerts, 2, nil).Once() - mockNotificationService.EXPECT().BuildFromAlerts(mock.AnythingOfType("[]alert.Alert"), mock.AnythingOfType("int"), mock.AnythingOfType("time.Time")).Return([]notification.Notification{}, nil) - mockNotificationService.EXPECT().Dispatch(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("notification.Notification")).Return("", nil) + Return(dummyAlerts, nil).Once() + mockNotificationService.EXPECT().Dispatch(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("[]notification.Notification"), mock.AnythingOfType("string")).Return(nil, nil) res, err := dummyGRPCServer.CreateAlerts(context.TODO(), dummyReq) assert.Equal(t, 1, len(res.GetAlerts())) diff --git a/internal/api/v1beta1/notification.go b/internal/api/v1beta1/notification.go index 661d25e7..806a88a2 100644 --- a/internal/api/v1beta1/notification.go +++ b/internal/api/v1beta1/notification.go @@ -3,6 +3,7 @@ package v1beta1 import ( "context" "fmt" + "strings" "github.com/goto/siren/core/notification" "github.com/goto/siren/core/template" @@ -66,27 +67,87 @@ func (s *GRPCServer) PostNotification(ctx context.Context, req *sirenv1beta1.Pos notificationTemplate = req.GetTemplate() } - n := notification.Notification{ - Type: notification.TypeEvent, - Data: req.GetData().AsMap(), - Labels: req.GetLabels(), - Template: notificationTemplate, - ReceiverSelectors: receiverSelectors, + notificationIDs, err := s.notificationService.Dispatch(ctx, []notification.Notification{ + { + Type: notification.TypeEvent, + Data: req.GetData().AsMap(), + Labels: req.GetLabels(), + Template: notificationTemplate, + ReceiverSelectors: receiverSelectors, + }, + }, notification.DispatchKindSingleNotification) + if err != nil { + return nil, s.generateRPCErr(err) + } + + if len(notificationIDs) != 1 { + return nil, s.generateRPCErr(errors.ErrInternal.WithMsgf("should send 1 notification only but got %d", len(notificationIDs))) + } + + if idempotencyKey != "" { + if err := s.notificationService.InsertIdempotency(ctx, idempotencyScope, idempotencyKey, notificationIDs[0]); err != nil { + return nil, s.generateRPCErr(err) + } + } + + return &sirenv1beta1.PostNotificationResponse{ + NotificationId: notificationIDs[0], + }, nil +} + +func (s *GRPCServer) PostBulkNotifications(ctx context.Context, req *sirenv1beta1.PostBulkNotificationsRequest) (*sirenv1beta1.PostBulkNotificationsResponse, error) { + if len(req.GetNotifications()) == 0 { + return nil, s.generateRPCErr(errors.ErrInvalid.WithMsgf("no bulk notifications found")) + } + idempotencyScope := api.GetHeaderString(ctx, s.headers.IdempotencyScope) + if idempotencyScope == "" { + idempotencyScope = notificationAPIScope } - notificationID, err := s.notificationService.Dispatch(ctx, n) + idempotencyKey := api.GetHeaderString(ctx, s.headers.IdempotencyKey) + if idempotencyKey != "" { + if notificationIDs, err := s.notificationService.CheckIdempotency(ctx, idempotencyScope, idempotencyKey); notificationIDs != "" { + splittedNotificationIDs := strings.Split(notificationIDs, ",") + return &sirenv1beta1.PostBulkNotificationsResponse{ + NotificationIds: splittedNotificationIDs, + }, nil + } else if errors.Is(err, errors.ErrNotFound) { + s.logger.Debug("no idempotency found with detail", "scope", idempotencyScope, "key", idempotencyKey) + } else { + return nil, s.generateRPCErr(fmt.Errorf("error when checking idempotency: %w", err)) + } + } + + notifications := []notification.Notification{} + + for _, nProto := range req.GetNotifications() { + var notificationTemplate = template.ReservedName_SystemDefault + if nProto.GetTemplate() != "" { + notificationTemplate = nProto.GetTemplate() + } + notifications = append(notifications, notification.Notification{ + NamespaceID: nProto.GetNamespaceId(), + Type: notification.TypeEvent, + Data: nProto.GetData().AsMap(), + Labels: nProto.GetLabels(), + ValidDuration: nProto.GetValidDuration().AsDuration(), + Template: notificationTemplate, + }) + } + + notificationIDs, err := s.notificationService.Dispatch(ctx, notifications, notification.DispatchKindBulkNotification) if err != nil { return nil, s.generateRPCErr(err) } if idempotencyKey != "" { - if err := s.notificationService.InsertIdempotency(ctx, idempotencyScope, idempotencyKey, notificationID); err != nil { + if err := s.notificationService.InsertIdempotency(ctx, idempotencyScope, idempotencyKey, strings.Join(notificationIDs, ",")); err != nil { return nil, s.generateRPCErr(err) } } - return &sirenv1beta1.PostNotificationResponse{ - NotificationId: notificationID, + return &sirenv1beta1.PostBulkNotificationsResponse{ + NotificationIds: notificationIDs, }, nil } diff --git a/internal/api/v1beta1/notification_test.go b/internal/api/v1beta1/notification_test.go index 7f87ec8c..e6a281f0 100644 --- a/internal/api/v1beta1/notification_test.go +++ b/internal/api/v1beta1/notification_test.go @@ -32,7 +32,7 @@ func TestGRPCServer_PostNotification(t *testing.T) { idempotencyKey: "test", setup: func(ns *mocks.NotificationService) { ns.EXPECT().CheckIdempotency(mock.AnythingOfType("*context.valueCtx"), mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return("", errors.ErrNotFound) - ns.EXPECT().Dispatch(mock.AnythingOfType("*context.valueCtx"), mock.AnythingOfType("notification.Notification")).Return("", errors.ErrInvalid) + ns.EXPECT().Dispatch(mock.AnythingOfType("*context.valueCtx"), mock.AnythingOfType("[]notification.Notification"), mock.AnythingOfType("string")).Return(nil, errors.ErrInvalid) }, errString: "rpc error: code = InvalidArgument desc = request is not valid", }, @@ -41,7 +41,7 @@ func TestGRPCServer_PostNotification(t *testing.T) { idempotencyKey: "test", setup: func(ns *mocks.NotificationService) { ns.EXPECT().CheckIdempotency(mock.AnythingOfType("*context.valueCtx"), mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return("", errors.ErrNotFound) - ns.EXPECT().Dispatch(mock.AnythingOfType("*context.valueCtx"), mock.AnythingOfType("notification.Notification")).Return("", errors.New("some error")) + ns.EXPECT().Dispatch(mock.AnythingOfType("*context.valueCtx"), mock.AnythingOfType("[]notification.Notification"), mock.AnythingOfType("string")).Return(nil, errors.New("some error")) }, errString: "rpc error: code = Internal desc = some unexpected error occurred", }, @@ -65,7 +65,7 @@ func TestGRPCServer_PostNotification(t *testing.T) { idempotencyKey: "test", setup: func(ns *mocks.NotificationService) { ns.EXPECT().CheckIdempotency(mock.AnythingOfType("*context.valueCtx"), mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return("", errors.ErrNotFound) - ns.EXPECT().Dispatch(mock.AnythingOfType("*context.valueCtx"), mock.AnythingOfType("notification.Notification")).Return(notificationID, nil) + ns.EXPECT().Dispatch(mock.AnythingOfType("*context.valueCtx"), mock.AnythingOfType("[]notification.Notification"), mock.AnythingOfType("string")).Return([]string{notificationID}, nil) ns.EXPECT().InsertIdempotency(mock.AnythingOfType("*context.valueCtx"), mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(errors.New("some error")) }, errString: "rpc error: code = Internal desc = some unexpected error occurred", @@ -75,7 +75,7 @@ func TestGRPCServer_PostNotification(t *testing.T) { idempotencyKey: "test", setup: func(ns *mocks.NotificationService) { ns.EXPECT().CheckIdempotency(mock.AnythingOfType("*context.valueCtx"), mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return("", errors.ErrNotFound) - ns.EXPECT().Dispatch(mock.AnythingOfType("*context.valueCtx"), mock.AnythingOfType("notification.Notification")).Return(notificationID, nil) + ns.EXPECT().Dispatch(mock.AnythingOfType("*context.valueCtx"), mock.AnythingOfType("[]notification.Notification"), mock.AnythingOfType("string")).Return([]string{notificationID}, nil) ns.EXPECT().InsertIdempotency(mock.AnythingOfType("*context.valueCtx"), mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(nil) }, }, diff --git a/internal/store/postgres/alerts.go b/internal/store/postgres/alerts.go index ebd57d1d..cb3fdb63 100644 --- a/internal/store/postgres/alerts.go +++ b/internal/store/postgres/alerts.go @@ -48,6 +48,7 @@ func NewAlertRepository(client *pgc.Client) *AlertRepository { return &AlertRepository{client} } +// Deprecated: replaced by BulkCreate func (r AlertRepository) Create(ctx context.Context, alrt alert.Alert) (alert.Alert, error) { var alertModel model.Alert alertModel.FromDomain(alrt) diff --git a/internal/store/postgres/alerts_test.go b/internal/store/postgres/alerts_test.go index ee4d5c05..a8386b31 100644 --- a/internal/store/postgres/alerts_test.go +++ b/internal/store/postgres/alerts_test.go @@ -10,6 +10,7 @@ import ( "github.com/goto/salt/dockertestx" "github.com/goto/salt/log" "github.com/goto/siren/core/alert" + "github.com/goto/siren/core/silence" "github.com/goto/siren/internal/store/postgres" "github.com/goto/siren/pkg/pgc" "github.com/ory/dockertest/v3" @@ -210,7 +211,7 @@ func (s *AlertsRepositoryTestSuite) TestBulkUpdateSilence() { var testCases = []testCase{ { Description: "should update 2 alerts to silence", - SilenceStatus: alert.SilenceStatusTotal, + SilenceStatus: silence.StatusTotal, ExpectedAlerts: []alert.Alert{ { ID: 2, @@ -220,7 +221,7 @@ func (s *AlertsRepositoryTestSuite) TestBulkUpdateSilence() { MetricValue: "97.95", Severity: "WARNING", Rule: "cpu-usage", - SilenceStatus: alert.SilenceStatusTotal, + SilenceStatus: silence.StatusTotal, }, { ID: 3, @@ -230,7 +231,7 @@ func (s *AlertsRepositoryTestSuite) TestBulkUpdateSilence() { MetricValue: "98.30", Severity: "CRITICAL", Rule: "cpu-usage", - SilenceStatus: alert.SilenceStatusTotal, + SilenceStatus: silence.StatusTotal, }, }, }, diff --git a/internal/store/postgres/notification.go b/internal/store/postgres/notification.go index d1bc1440..5795099d 100644 --- a/internal/store/postgres/notification.go +++ b/internal/store/postgres/notification.go @@ -20,6 +20,13 @@ INSERT INTO notifications (namespace_id, type, data, labels, valid_duration, tem RETURNING * ` +const notificationInsertNamedQuery = ` +INSERT INTO notifications + (namespace_id, type, data, labels, valid_duration, template, unique_key, receiver_selectors, created_at) + VALUES (:namespace_id, :type, :data, :labels, :valid_duration, :template, :unique_key, :receiver_selectors, now()) +RETURNING * +` + var notificationListQueryBuilder = sq.Select( "id", "namespace_id", @@ -75,6 +82,40 @@ func (r *NotificationRepository) Create(ctx context.Context, n notification.Noti return *newNModel.ToDomain(), nil } +func (r *NotificationRepository) BulkCreate(ctx context.Context, ns []notification.Notification) ([]notification.Notification, error) { + var notificationsModel = []model.Notification{} + + for _, n := range ns { + nModel := new(model.Notification) + nModel.FromDomain(n) + notificationsModel = append(notificationsModel, *nModel) + } + + ctx = otelsql.WithCustomAttributes( + ctx, + []attribute.KeyValue{ + attribute.String("db.repository.method", "BulkCreate"), + attribute.String("db.sql.table", "notifications"), + }..., + ) + + rows, err := r.client.NamedQueryContext(ctx, notificationInsertNamedQuery, notificationsModel) + if err != nil { + return nil, err + } + defer rows.Close() + + notificationsDomain := []notification.Notification{} + for rows.Next() { + var notificationModel model.Notification + if err := rows.StructScan(¬ificationModel); err != nil { + return nil, err + } + notificationsDomain = append(notificationsDomain, *notificationModel.ToDomain()) + } + return notificationsDomain, nil +} + func (r *NotificationRepository) List(ctx context.Context, flt notification.Filter) ([]notification.Notification, error) { var queryBuilder = notificationListQueryBuilder diff --git a/internal/store/postgres/notification_test.go b/internal/store/postgres/notification_test.go index 078eb628..f209884d 100644 --- a/internal/store/postgres/notification_test.go +++ b/internal/store/postgres/notification_test.go @@ -128,6 +128,77 @@ func (s *NotificationRepositoryTestSuite) TestCreate() { } } +func (s *NotificationRepositoryTestSuite) TestBulkCreate() { + type testCase struct { + Description string + NotificationsToCreate []notification.Notification + NumReturnedNotifications int + ErrString string + } + + var testCases = []testCase{ + { + Description: "should create bulk notifications", + NotificationsToCreate: []notification.Notification{ + { + NamespaceID: 1, + Type: notification.TypeAlert, + Data: map[string]any{}, + Labels: map[string]string{}, + CreatedAt: time.Now(), + }, + { + NamespaceID: 2, + Type: notification.TypeAlert, + Data: map[string]any{}, + Labels: map[string]string{}, + CreatedAt: time.Now(), + }, + }, + NumReturnedNotifications: 2, + }, + { + Description: "should return error if a notification is invalid", + NotificationsToCreate: []notification.Notification{ + { + NamespaceID: 1, + Type: notification.TypeAlert, + Data: map[string]any{ + "k1": func(x chan struct{}) { + <-x + }, + }, + Labels: map[string]string{}, + CreatedAt: time.Now(), + }, + { + NamespaceID: 2, + Type: notification.TypeAlert, + Data: map[string]any{}, + Labels: map[string]string{}, + CreatedAt: time.Now(), + }, + }, + ErrString: "sql: converting argument $3 type: json: unsupported type: func(chan struct {})", + }, + } + + for _, tc := range testCases { + s.Run(tc.Description, func() { + notifications, err := s.repository.BulkCreate(s.ctx, tc.NotificationsToCreate) + if err != nil { + if err.Error() != tc.ErrString { + s.T().Fatalf("got error %s, expected was %s", err.Error(), tc.ErrString) + } + } else { + if len(notifications) != int(tc.NumReturnedNotifications) { + s.T().Fatalf("got num created notifications %d, expected was %d", len(notifications), tc.NumReturnedNotifications) + } + } + }) + } +} + func (s *NotificationRepositoryTestSuite) TestList() { type testCase struct { Description string diff --git a/pkg/pgc/client.go b/pkg/pgc/client.go index ed5c9c23..5818cc52 100644 --- a/pkg/pgc/client.go +++ b/pkg/pgc/client.go @@ -114,6 +114,10 @@ func (c *Client) NamedExecContext(ctx context.Context, query string, arg interfa return sqlx.NamedExecContext(ctx, c.GetDB(ctx), query, arg) } +func (c *Client) NamedQueryContext(ctx context.Context, query string, arg interface{}) (*sqlx.Rows, error) { + return sqlx.NamedQueryContext(ctx, c.GetDB(ctx), query, arg) +} + func (c *Client) WithTransaction(ctx context.Context, opts *sql.TxOptions) context.Context { var ( tx *sqlx.Tx diff --git a/pkg/pgc/type.go b/pkg/pgc/type.go index c06301c2..25509497 100644 --- a/pkg/pgc/type.go +++ b/pkg/pgc/type.go @@ -96,3 +96,24 @@ func (a ListStringStringMap) Value() (driver.Value, error) { } return json.Marshal(a) } + +type ListString []string + +func (m *ListString) Scan(value interface{}) error { + if value == nil { + m = new(ListString) + return nil + } + b, ok := value.([]byte) + if !ok { + return errors.New("failed type assertion to []byte") + } + return json.Unmarshal(b, &m) +} + +func (a ListString) Value() (driver.Value, error) { + if len(a) == 0 { + return nil, nil + } + return json.Marshal(a) +} diff --git a/pkg/structure/structure.go b/pkg/structure/structure.go new file mode 100644 index 00000000..7833a576 --- /dev/null +++ b/pkg/structure/structure.go @@ -0,0 +1,61 @@ +package structure + +import ( + "crypto/sha256" + "fmt" + + "github.com/mitchellh/hashstructure/v2" +) + +func RemoveDuplicate[T comparable](aSlice []T) []T { + keys := make(map[T]bool) + list := []T{} + + for _, v := range aSlice { + if _, value := keys[v]; !value { + keys[v] = true + list = append(list, v) + } + } + return list +} + +func GroupByLabels[S any](collection []S, groupBy []string, getLabels func(S) map[string]string) (map[uint64][]S, error) { + var collectionMap = map[uint64][]S{} + + for _, c := range collection { + var labels = getLabels(c) + var groupLabels = BuildGroupLabels(labels, groupBy) + if len(groupLabels) == 0 { + groupLabels = labels + } + hash, err := hashstructure.Hash(groupLabels, hashstructure.FormatV2, nil) + if err != nil { + return nil, fmt.Errorf("cannot get hash from alert %v", c) + } + collectionMap[hash] = append(collectionMap[hash], c) + } + + return collectionMap, nil +} + +func BuildGroupLabels(labels map[string]string, groupBy []string) map[string]string { + var groupLabels = map[string]string{} + + for _, g := range groupBy { + if v, ok := labels[g]; ok { + groupLabels[g] = v + } + } + + return groupLabels +} + +// HashGroupKey hash groupKey from alert and hashKey from labels +func HashGroupKey(groupKey string, hashKey uint64) string { + h := sha256.New() + // hash.Hash.Write never returns an error. + //nolint: errcheck + h.Write([]byte(fmt.Sprintf("%s%d", groupKey, hashKey))) + return fmt.Sprintf("%x", h.Sum(nil)) +} diff --git a/core/notification/utils_test.go b/pkg/structure/structure_test.go similarity index 89% rename from core/notification/utils_test.go rename to pkg/structure/structure_test.go index ead368bf..cbe5400a 100644 --- a/core/notification/utils_test.go +++ b/pkg/structure/structure_test.go @@ -1,10 +1,9 @@ -package notification +package structure import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/goto/siren/core/alert" "github.com/mitchellh/hashstructure/v2" "github.com/stretchr/testify/require" ) @@ -33,7 +32,7 @@ func Test_removeDuplicateStringValues(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := removeDuplicateStringValues(tt.strSlice) + got := RemoveDuplicate(tt.strSlice) if diff := cmp.Diff(got, tt.want); diff != "" { t.Errorf("got diff = %v", diff) } @@ -61,15 +60,20 @@ func Test_groupByLabels(t *testing.T) { }, hashstructure.FormatV2, nil) require.NoError(t, err) + type mockAlert struct { + ID uint64 + Labels map[string]string + } + tests := []struct { name string - alerts []alert.Alert - want map[uint64][]alert.Alert + alerts []mockAlert + want map[uint64][]mockAlert wantErr bool }{ { name: "shoudl group alerts if labels are same", - alerts: []alert.Alert{ + alerts: []mockAlert{ { ID: 12, Labels: map[string]string{ @@ -106,7 +110,7 @@ func Test_groupByLabels(t *testing.T) { }, }, }, - want: map[uint64][]alert.Alert{ + want: map[uint64][]mockAlert{ hashKey1: { { ID: 12, @@ -153,7 +157,7 @@ func Test_groupByLabels(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := groupByLabels(tt.alerts, groupBy) + got, err := GroupByLabels(tt.alerts, groupBy, func(a mockAlert) map[string]string { return a.Labels }) if (err != nil) != tt.wantErr { t.Errorf("groupByLabels() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/plugins/providers/cortex/go.mod b/plugins/providers/cortex/go.mod index 33870951..b4453a12 100644 --- a/plugins/providers/cortex/go.mod +++ b/plugins/providers/cortex/go.mod @@ -77,12 +77,13 @@ require ( go.uber.org/goleak v1.3.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/net v0.22.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d // indirect + golang.org/x/net v0.25.0 // indirect golang.org/x/oauth2 v0.16.0 // indirect - golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe // indirect diff --git a/plugins/providers/cortex/go.sum b/plugins/providers/cortex/go.sum index 02639262..dfed2168 100644 --- a/plugins/providers/cortex/go.sum +++ b/plugins/providers/cortex/go.sum @@ -2715,7 +2715,7 @@ golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4 golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2734,6 +2734,7 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/exp v0.0.0-20200821190819-94841d0725da/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d h1:N0hmiNbwsSNwHBAvR3QB5w25pUwH4tK0Y/RltD1j1h4= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -2878,7 +2879,7 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -2935,7 +2936,7 @@ golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -3100,7 +3101,7 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= @@ -3135,7 +3136,7 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/plugins/queues/postgresq/migrations/000005_add_notification_ids_column.down.sql b/plugins/queues/postgresq/migrations/000005_add_notification_ids_column.down.sql new file mode 100644 index 00000000..15304edc --- /dev/null +++ b/plugins/queues/postgresq/migrations/000005_add_notification_ids_column.down.sql @@ -0,0 +1,5 @@ +DROP INDEX IF EXISTS notification_ids_idx; + +ALTER TABLE + message_queue +DROP COLUMN IF EXISTS notification_ids; \ No newline at end of file diff --git a/plugins/queues/postgresq/migrations/000005_add_notification_ids_column.up.sql b/plugins/queues/postgresq/migrations/000005_add_notification_ids_column.up.sql new file mode 100644 index 00000000..faa51a7b --- /dev/null +++ b/plugins/queues/postgresq/migrations/000005_add_notification_ids_column.up.sql @@ -0,0 +1,5 @@ +ALTER TABLE + message_queue +ADD COLUMN IF NOT EXISTS notification_ids jsonb; + +CREATE INDEX IF NOT EXISTS notification_ids_idx ON message_queue USING gin (notification_ids); \ No newline at end of file diff --git a/plugins/queues/postgresq/migrations/000006_migrate_notification_id_to_notification_ids.down.sql b/plugins/queues/postgresq/migrations/000006_migrate_notification_id_to_notification_ids.down.sql new file mode 100644 index 00000000..7029a3d3 --- /dev/null +++ b/plugins/queues/postgresq/migrations/000006_migrate_notification_id_to_notification_ids.down.sql @@ -0,0 +1,7 @@ +UPDATE message_queue SET notification_id = notification_ids->>0 WHERE notification_ids IS NOT NULL AND notification_id IS NULL; + +ALTER TABLE + message_queue +ADD COLUMN IF NOT EXISTS notification_id text; + +CREATE INDEX IF NOT EXISTS message_queue_notification_id_idx ON message_queue (notification_id); \ No newline at end of file diff --git a/plugins/queues/postgresq/migrations/000006_migrate_notification_id_to_notification_ids.up.sql b/plugins/queues/postgresq/migrations/000006_migrate_notification_id_to_notification_ids.up.sql new file mode 100644 index 00000000..e006d375 --- /dev/null +++ b/plugins/queues/postgresq/migrations/000006_migrate_notification_id_to_notification_ids.up.sql @@ -0,0 +1,7 @@ +UPDATE message_queue SET notification_ids = json_build_array(notification_id) WHERE notification_id IS NOT NULL AND notification_ids IS NULL; + +DROP INDEX IF EXISTS message_queue_notification_id_idx; + +ALTER TABLE + message_queue +DROP COLUMN IF EXISTS notification_id; \ No newline at end of file diff --git a/plugins/queues/postgresq/model.go b/plugins/queues/postgresq/model.go index 8233b177..4654409e 100644 --- a/plugins/queues/postgresq/model.go +++ b/plugins/queues/postgresq/model.go @@ -9,8 +9,8 @@ import ( ) type NotificationMessage struct { - ID string `db:"id"` - NotificationID string `db:"notification_id"` + ID string `db:"id"` + NotificationIDs pgc.ListString `db:"notification_ids"` Status string `db:"status"` @@ -31,7 +31,7 @@ type NotificationMessage struct { func (nm *NotificationMessage) FromDomain(domainMessage notification.Message) { nm.ID = domainMessage.ID - nm.NotificationID = domainMessage.NotificationID + nm.NotificationIDs = domainMessage.NotificationIDs nm.Status = string(domainMessage.Status) nm.ReceiverType = domainMessage.ReceiverType nm.Configs = domainMessage.Configs @@ -60,9 +60,9 @@ func (nm *NotificationMessage) FromDomain(domainMessage notification.Message) { func (nm *NotificationMessage) ToDomain() notification.Message { return notification.Message{ - ID: nm.ID, - NotificationID: nm.NotificationID, - Status: notification.MessageStatus(nm.Status), + ID: nm.ID, + NotificationIDs: nm.NotificationIDs, + Status: notification.MessageStatus(nm.Status), ReceiverType: nm.ReceiverType, Configs: nm.Configs, diff --git a/plugins/queues/postgresq/queue.go b/plugins/queues/postgresq/queue.go index adb1ce16..f1731ad6 100644 --- a/plugins/queues/postgresq/queue.go +++ b/plugins/queues/postgresq/queue.go @@ -48,13 +48,13 @@ WHERE id = $6 queueEnqueueNamedQuery = fmt.Sprintf(` INSERT INTO %s - (id, notification_id, status, receiver_type, configs, details, last_error, max_tries, try_count, retryable, expired_at, created_at, updated_at) - VALUES (:id,:notification_id,:status,:receiver_type,:configs,:details,:last_error,:max_tries,:try_count,:retryable,:expired_at,:created_at,:updated_at) + (id, notification_ids, status, receiver_type, configs, details, last_error, max_tries, try_count, retryable, expired_at, created_at, updated_at) + VALUES (:id,:notification_ids,:status,:receiver_type,:configs,:details,:last_error,:max_tries,:try_count,:retryable,:expired_at,:created_at,:updated_at) `, MessageQueueTableFullName) messagesListQueryBuilder = sq.Select( "id", - "notification_id", + "notification_ids", "status", "receiver_type", "configs", @@ -81,7 +81,7 @@ WHERE id IN ( FOR UPDATE SKIP LOCKED LIMIT %d ) -RETURNING * +RETURNING id, status, receiver_type, configs, details, last_error, max_tries, try_count, retryable, expired_at, created_at, updated_at `, MessageQueueTableFullName, notification.MessageStatusPending, MessageQueueTableFullName, notification.MessageStatusEnqueued, notification.MessageStatusPending, receiverTypesList, batchSize) } @@ -97,7 +97,7 @@ WHERE id IN ( FOR UPDATE SKIP LOCKED LIMIT %d ) -RETURNING * +RETURNING id, status, receiver_type, configs, details, last_error, max_tries, try_count, retryable, expired_at, created_at, updated_at `, MessageQueueTableFullName, notification.MessageStatusPending, MessageQueueTableFullName, notification.MessageStatusFailed, notification.MessageStatusPending, receiverTypesList, batchSize) } @@ -241,9 +241,9 @@ func (q *Queue) Stop(ctx context.Context) error { } func (q *Queue) ListMessages(ctx context.Context, notificationID string) ([]notification.Message, error) { - var queryBuilder = messagesListQueryBuilder.Where("notification_id = ?", notificationID) + var queryBuilder = messagesListQueryBuilder.Where(fmt.Sprintf("notification_ids ? '%s'", notificationID)) - query, args, err := queryBuilder.PlaceholderFormat(sq.Dollar).ToSql() + query, args, err := queryBuilder.ToSql() if err != nil { return nil, err } diff --git a/plugins/queues/postgresq/queue_test.go b/plugins/queues/postgresq/queue_test.go index b0069d04..c461c6d0 100644 --- a/plugins/queues/postgresq/queue_test.go +++ b/plugins/queues/postgresq/queue_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "slices" "testing" "time" @@ -32,48 +33,48 @@ var ( messagesGenerator = func() []notification.Message { return []notification.Message{ { - ID: uuid.NewString(), - NotificationID: notificationIDs[0], - ReceiverType: receiver.TypeSlack, - Configs: map[string]any{}, - Status: notification.MessageStatusEnqueued, - Details: map[string]any{}, - CreatedAt: timeNow, - UpdatedAt: timeNow, + ID: uuid.NewString(), + NotificationIDs: []string{notificationIDs[0]}, + ReceiverType: receiver.TypeSlack, + Configs: map[string]any{}, + Status: notification.MessageStatusEnqueued, + Details: map[string]any{}, + CreatedAt: timeNow, + UpdatedAt: timeNow, }, { - ID: uuid.NewString(), - NotificationID: notificationIDs[0], - ReceiverType: receiver.TypeSlack, - Configs: map[string]any{}, - Status: notification.MessageStatusEnqueued, - Details: map[string]any{}, - CreatedAt: timeNow, - UpdatedAt: timeNow, + ID: uuid.NewString(), + NotificationIDs: []string{notificationIDs[0]}, + ReceiverType: receiver.TypeSlack, + Configs: map[string]any{}, + Status: notification.MessageStatusEnqueued, + Details: map[string]any{}, + CreatedAt: timeNow, + UpdatedAt: timeNow, }, { - ID: uuid.NewString(), - NotificationID: notificationIDs[1], - ReceiverType: receiver.TypeSlack, - Status: notification.MessageStatusEnqueued, - CreatedAt: timeNow, - UpdatedAt: timeNow, + ID: uuid.NewString(), + NotificationIDs: []string{notificationIDs[1]}, + ReceiverType: receiver.TypeSlack, + Status: notification.MessageStatusEnqueued, + CreatedAt: timeNow, + UpdatedAt: timeNow, }, { - ID: uuid.NewString(), - NotificationID: notificationIDs[1], - ReceiverType: receiver.TypeSlack, - Status: notification.MessageStatusEnqueued, - CreatedAt: timeNow, - UpdatedAt: timeNow, + ID: uuid.NewString(), + NotificationIDs: []string{notificationIDs[1]}, + ReceiverType: receiver.TypeSlack, + Status: notification.MessageStatusEnqueued, + CreatedAt: timeNow, + UpdatedAt: timeNow, }, { - ID: uuid.NewString(), - NotificationID: notificationIDs[2], - ReceiverType: receiver.TypeSlack, - Status: notification.MessageStatusEnqueued, - CreatedAt: timeNow, - UpdatedAt: timeNow, + ID: uuid.NewString(), + NotificationIDs: []string{notificationIDs[2]}, + ReceiverType: receiver.TypeSlack, + Status: notification.MessageStatusEnqueued, + CreatedAt: timeNow, + UpdatedAt: timeNow, }, } } @@ -311,12 +312,12 @@ func (s *QueueTestSuite) TestListNotificationMessages() { expectedMessages := []notification.Message{} for _, msg := range messages { - if msg.NotificationID == messages[0].NotificationID { + if slices.Contains(msg.NotificationIDs, messages[0].NotificationIDs[0]) { expectedMessages = append(expectedMessages, msg) } } - result, err := s.q.ListMessages(context.Background(), messages[0].NotificationID) + result, err := s.q.ListMessages(context.Background(), messages[0].NotificationIDs[0]) s.Require().NoError(err) if diff := cmp.Diff(result, expectedMessages, diff --git a/proto/gotocompany/siren/v1beta1/siren.pb.go b/proto/gotocompany/siren/v1beta1/siren.pb.go index fb57254a..305eb684 100644 --- a/proto/gotocompany/siren/v1beta1/siren.pb.go +++ b/proto/gotocompany/siren/v1beta1/siren.pb.go @@ -5232,6 +5232,100 @@ func (x *PostNotificationResponse) GetNotificationId() string { return "" } +type PostBulkNotificationsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Notifications []*Notification `protobuf:"bytes,1,rep,name=notifications,proto3" json:"notifications,omitempty"` +} + +func (x *PostBulkNotificationsRequest) Reset() { + *x = PostBulkNotificationsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[85] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PostBulkNotificationsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PostBulkNotificationsRequest) ProtoMessage() {} + +func (x *PostBulkNotificationsRequest) ProtoReflect() protoreflect.Message { + mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[85] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PostBulkNotificationsRequest.ProtoReflect.Descriptor instead. +func (*PostBulkNotificationsRequest) Descriptor() ([]byte, []int) { + return file_gotocompany_siren_v1beta1_siren_proto_rawDescGZIP(), []int{85} +} + +func (x *PostBulkNotificationsRequest) GetNotifications() []*Notification { + if x != nil { + return x.Notifications + } + return nil +} + +type PostBulkNotificationsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NotificationIds []string `protobuf:"bytes,1,rep,name=notification_ids,json=notificationIds,proto3" json:"notification_ids,omitempty"` +} + +func (x *PostBulkNotificationsResponse) Reset() { + *x = PostBulkNotificationsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[86] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PostBulkNotificationsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PostBulkNotificationsResponse) ProtoMessage() {} + +func (x *PostBulkNotificationsResponse) ProtoReflect() protoreflect.Message { + mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[86] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PostBulkNotificationsResponse.ProtoReflect.Descriptor instead. +func (*PostBulkNotificationsResponse) Descriptor() ([]byte, []int) { + return file_gotocompany_siren_v1beta1_siren_proto_rawDescGZIP(), []int{86} +} + +func (x *PostBulkNotificationsResponse) GetNotificationIds() []string { + if x != nil { + return x.NotificationIds + } + return nil +} + type ListNotificationMessagesRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -5243,7 +5337,7 @@ type ListNotificationMessagesRequest struct { func (x *ListNotificationMessagesRequest) Reset() { *x = ListNotificationMessagesRequest{} if protoimpl.UnsafeEnabled { - mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[85] + mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5256,7 +5350,7 @@ func (x *ListNotificationMessagesRequest) String() string { func (*ListNotificationMessagesRequest) ProtoMessage() {} func (x *ListNotificationMessagesRequest) ProtoReflect() protoreflect.Message { - mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[85] + mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[87] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5269,7 +5363,7 @@ func (x *ListNotificationMessagesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListNotificationMessagesRequest.ProtoReflect.Descriptor instead. func (*ListNotificationMessagesRequest) Descriptor() ([]byte, []int) { - return file_gotocompany_siren_v1beta1_siren_proto_rawDescGZIP(), []int{85} + return file_gotocompany_siren_v1beta1_siren_proto_rawDescGZIP(), []int{87} } func (x *ListNotificationMessagesRequest) GetNotificationId() string { @@ -5284,25 +5378,25 @@ type NotificationMessage struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - NotificationId string `protobuf:"bytes,2,opt,name=notification_id,json=notificationId,proto3" json:"notification_id,omitempty"` - Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"` - ReceiverType string `protobuf:"bytes,4,opt,name=receiver_type,json=receiverType,proto3" json:"receiver_type,omitempty"` - Details *structpb.Struct `protobuf:"bytes,5,opt,name=details,proto3" json:"details,omitempty"` - LastError string `protobuf:"bytes,6,opt,name=last_error,json=lastError,proto3" json:"last_error,omitempty"` - MaxTries uint64 `protobuf:"varint,7,opt,name=max_tries,json=maxTries,proto3" json:"max_tries,omitempty"` - TryCount uint64 `protobuf:"varint,8,opt,name=try_count,json=tryCount,proto3" json:"try_count,omitempty"` - Retryable bool `protobuf:"varint,9,opt,name=retryable,proto3" json:"retryable,omitempty"` - ExpiredAt *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=expired_at,json=expiredAt,proto3" json:"expired_at,omitempty"` - CreatedAt *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` - UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,12,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` - Configs *structpb.Struct `protobuf:"bytes,13,opt,name=configs,proto3" json:"configs,omitempty"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + NotificationIds []string `protobuf:"bytes,2,rep,name=notification_ids,json=notificationIds,proto3" json:"notification_ids,omitempty"` + Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"` + ReceiverType string `protobuf:"bytes,4,opt,name=receiver_type,json=receiverType,proto3" json:"receiver_type,omitempty"` + Details *structpb.Struct `protobuf:"bytes,5,opt,name=details,proto3" json:"details,omitempty"` + LastError string `protobuf:"bytes,6,opt,name=last_error,json=lastError,proto3" json:"last_error,omitempty"` + MaxTries uint64 `protobuf:"varint,7,opt,name=max_tries,json=maxTries,proto3" json:"max_tries,omitempty"` + TryCount uint64 `protobuf:"varint,8,opt,name=try_count,json=tryCount,proto3" json:"try_count,omitempty"` + Retryable bool `protobuf:"varint,9,opt,name=retryable,proto3" json:"retryable,omitempty"` + ExpiredAt *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=expired_at,json=expiredAt,proto3" json:"expired_at,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,12,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + Configs *structpb.Struct `protobuf:"bytes,13,opt,name=configs,proto3" json:"configs,omitempty"` } func (x *NotificationMessage) Reset() { *x = NotificationMessage{} if protoimpl.UnsafeEnabled { - mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[86] + mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5315,7 +5409,7 @@ func (x *NotificationMessage) String() string { func (*NotificationMessage) ProtoMessage() {} func (x *NotificationMessage) ProtoReflect() protoreflect.Message { - mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[86] + mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[88] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5328,7 +5422,7 @@ func (x *NotificationMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use NotificationMessage.ProtoReflect.Descriptor instead. func (*NotificationMessage) Descriptor() ([]byte, []int) { - return file_gotocompany_siren_v1beta1_siren_proto_rawDescGZIP(), []int{86} + return file_gotocompany_siren_v1beta1_siren_proto_rawDescGZIP(), []int{88} } func (x *NotificationMessage) GetId() string { @@ -5338,11 +5432,11 @@ func (x *NotificationMessage) GetId() string { return "" } -func (x *NotificationMessage) GetNotificationId() string { +func (x *NotificationMessage) GetNotificationIds() []string { if x != nil { - return x.NotificationId + return x.NotificationIds } - return "" + return nil } func (x *NotificationMessage) GetStatus() string { @@ -5433,7 +5527,7 @@ type ListNotificationMessagesResponse struct { func (x *ListNotificationMessagesResponse) Reset() { *x = ListNotificationMessagesResponse{} if protoimpl.UnsafeEnabled { - mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[87] + mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5446,7 +5540,7 @@ func (x *ListNotificationMessagesResponse) String() string { func (*ListNotificationMessagesResponse) ProtoMessage() {} func (x *ListNotificationMessagesResponse) ProtoReflect() protoreflect.Message { - mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[87] + mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[89] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5459,7 +5553,7 @@ func (x *ListNotificationMessagesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListNotificationMessagesResponse.ProtoReflect.Descriptor instead. func (*ListNotificationMessagesResponse) Descriptor() ([]byte, []int) { - return file_gotocompany_siren_v1beta1_siren_proto_rawDescGZIP(), []int{87} + return file_gotocompany_siren_v1beta1_siren_proto_rawDescGZIP(), []int{89} } func (x *ListNotificationMessagesResponse) GetMessages() []*NotificationMessage { @@ -5483,7 +5577,7 @@ type ListNotificationsRequest struct { func (x *ListNotificationsRequest) Reset() { *x = ListNotificationsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[88] + mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5496,7 +5590,7 @@ func (x *ListNotificationsRequest) String() string { func (*ListNotificationsRequest) ProtoMessage() {} func (x *ListNotificationsRequest) ProtoReflect() protoreflect.Message { - mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[88] + mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[90] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5509,7 +5603,7 @@ func (x *ListNotificationsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListNotificationsRequest.ProtoReflect.Descriptor instead. func (*ListNotificationsRequest) Descriptor() ([]byte, []int) { - return file_gotocompany_siren_v1beta1_siren_proto_rawDescGZIP(), []int{88} + return file_gotocompany_siren_v1beta1_siren_proto_rawDescGZIP(), []int{90} } func (x *ListNotificationsRequest) GetType() string { @@ -5551,7 +5645,7 @@ type ReceiverSelector struct { func (x *ReceiverSelector) Reset() { *x = ReceiverSelector{} if protoimpl.UnsafeEnabled { - mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[89] + mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5564,7 +5658,7 @@ func (x *ReceiverSelector) String() string { func (*ReceiverSelector) ProtoMessage() {} func (x *ReceiverSelector) ProtoReflect() protoreflect.Message { - mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[89] + mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[91] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5577,7 +5671,7 @@ func (x *ReceiverSelector) ProtoReflect() protoreflect.Message { // Deprecated: Use ReceiverSelector.ProtoReflect.Descriptor instead. func (*ReceiverSelector) Descriptor() ([]byte, []int) { - return file_gotocompany_siren_v1beta1_siren_proto_rawDescGZIP(), []int{89} + return file_gotocompany_siren_v1beta1_siren_proto_rawDescGZIP(), []int{91} } func (x *ReceiverSelector) GetReceiverSelector() map[string]string { @@ -5607,7 +5701,7 @@ type Notification struct { func (x *Notification) Reset() { *x = Notification{} if protoimpl.UnsafeEnabled { - mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[90] + mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5620,7 +5714,7 @@ func (x *Notification) String() string { func (*Notification) ProtoMessage() {} func (x *Notification) ProtoReflect() protoreflect.Message { - mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[90] + mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[92] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5633,7 +5727,7 @@ func (x *Notification) ProtoReflect() protoreflect.Message { // Deprecated: Use Notification.ProtoReflect.Descriptor instead. func (*Notification) Descriptor() ([]byte, []int) { - return file_gotocompany_siren_v1beta1_siren_proto_rawDescGZIP(), []int{90} + return file_gotocompany_siren_v1beta1_siren_proto_rawDescGZIP(), []int{92} } func (x *Notification) GetId() string { @@ -5717,7 +5811,7 @@ type ListNotificationsResponse struct { func (x *ListNotificationsResponse) Reset() { *x = ListNotificationsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[91] + mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5730,7 +5824,7 @@ func (x *ListNotificationsResponse) String() string { func (*ListNotificationsResponse) ProtoMessage() {} func (x *ListNotificationsResponse) ProtoReflect() protoreflect.Message { - mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[91] + mi := &file_gotocompany_siren_v1beta1_siren_proto_msgTypes[93] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5743,7 +5837,7 @@ func (x *ListNotificationsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListNotificationsResponse.ProtoReflect.Descriptor instead. func (*ListNotificationsResponse) Descriptor() ([]byte, []int) { - return file_gotocompany_siren_v1beta1_siren_proto_rawDescGZIP(), []int{91} + return file_gotocompany_siren_v1beta1_siren_proto_rawDescGZIP(), []int{93} } func (x *ListNotificationsResponse) GetNotifications() []*Notification { @@ -6660,613 +6754,639 @@ var file_gotocompany_siren_v1beta1_siren_proto_rawDesc = []byte{ 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, - 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x4a, - 0x0a, 0x1f, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x6f, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x99, 0x04, 0x0a, 0x13, 0x4e, - 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x69, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x6f, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x63, 0x65, - 0x69, 0x76, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x31, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, - 0x69, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, - 0x63, 0x74, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, - 0x61, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x6c, 0x61, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, - 0x78, 0x5f, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6d, - 0x61, 0x78, 0x54, 0x72, 0x69, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x72, 0x79, 0x5f, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x74, 0x72, 0x79, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x74, 0x72, 0x79, 0x61, 0x62, 0x6c, - 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x65, 0x74, 0x72, 0x79, 0x61, 0x62, - 0x6c, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x61, 0x74, - 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, - 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x64, 0x41, 0x74, 0x12, 0x31, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x18, 0x0d, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x07, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x22, 0x6e, 0x0a, 0x20, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, - 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x08, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x67, + 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x6d, + 0x0a, 0x1c, 0x50, 0x6f, 0x73, 0x74, 0x42, 0x75, 0x6c, 0x6b, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4d, + 0x0a, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, + 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, + 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x4a, 0x0a, + 0x1d, 0x50, 0x6f, 0x73, 0x74, 0x42, 0x75, 0x6c, 0x6b, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, + 0x0a, 0x10, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, + 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x22, 0x4a, 0x0a, 0x1f, 0x4c, 0x69, 0x73, + 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, + 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x9b, 0x04, 0x0a, 0x13, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x29, 0x0a, + 0x10, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, + 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x31, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, + 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x61, 0x73, 0x74, + 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c, 0x61, + 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x74, + 0x72, 0x69, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x54, + 0x72, 0x69, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x74, 0x72, 0x79, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x65, 0x74, 0x72, 0x79, 0x61, 0x62, 0x6c, 0x65, 0x12, + 0x39, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, + 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, + 0x5f, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, + 0x12, 0x31, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x73, 0x22, 0x6e, 0x0a, 0x20, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x67, 0x6f, 0x74, 0x6f, + 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x73, 0x22, 0x87, 0x05, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x12, 0xbf, 0x01, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x3f, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, + 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x42, 0x66, 0x92, 0x41, 0x63, 0x32, 0x61, 0x71, 0x75, 0x65, 0x72, 0x79, 0x20, 0x72, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, 0x62, 0x61, 0x73, 0x65, 0x64, 0x20, 0x6f, 0x6e, 0x20, 0x6e, + 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x73, 0x2e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x20, 0x69, 0x73, + 0x20, 0x77, 0x72, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x20, 0x61, 0x73, 0x20, 0x6d, 0x61, 0x70, 0x2e, + 0x20, 0x65, 0x67, 0x2c, 0x20, 0x22, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x5b, 0x6b, 0x65, 0x79, + 0x31, 0x5d, 0x3d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x31, 0x22, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x73, 0x12, 0xf7, 0x01, 0x0a, 0x12, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, + 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x4a, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, + 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x2e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x53, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x7c, 0x92, 0x41, 0x79, + 0x32, 0x77, 0x71, 0x75, 0x65, 0x72, 0x79, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, 0x62, + 0x61, 0x73, 0x65, 0x64, 0x20, 0x6f, 0x6e, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, + 0x20, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x20, 0x69, 0x73, 0x20, 0x77, 0x72, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x20, 0x61, 0x73, 0x20, + 0x6d, 0x61, 0x70, 0x2e, 0x20, 0x65, 0x67, 0x2c, 0x20, 0x22, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, + 0x65, 0x72, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5b, 0x6b, 0x65, 0x79, 0x31, + 0x5d, 0x3d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x31, 0x22, 0x52, 0x11, 0x72, 0x65, 0x63, 0x65, 0x69, + 0x76, 0x65, 0x72, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x1a, 0x39, 0x0a, 0x0b, + 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x44, 0x0a, 0x16, 0x52, 0x65, 0x63, 0x65, 0x69, + 0x76, 0x65, 0x72, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xc7, 0x01, + 0x0a, 0x10, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x12, 0x6e, 0x0a, 0x11, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x73, + 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x41, 0x2e, + 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, + 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, + 0x65, 0x72, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x52, 0x65, 0x63, 0x65, 0x69, + 0x76, 0x65, 0x72, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x10, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x1a, 0x43, 0x0a, 0x15, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x53, 0x65, + 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x9c, 0x04, 0x0a, 0x0c, 0x4e, 0x6f, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x61, 0x6d, 0x65, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, + 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, + 0x2b, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x4b, 0x0a, 0x06, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x08, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x22, 0x87, 0x05, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x4e, - 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x74, 0x65, 0x12, 0xbf, 0x01, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x03, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3f, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x40, 0x0a, 0x0e, 0x76, 0x61, 0x6c, + 0x69, 0x64, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x76, 0x61, + 0x6c, 0x69, 0x64, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x74, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x37, 0x0a, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x5f, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x74, + 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x4b, 0x65, 0x79, 0x12, + 0x5a, 0x0a, 0x12, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x67, 0x6f, + 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, + 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x11, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, + 0x65, 0x72, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, + 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x6a, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x67, 0x6f, 0x74, + 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x32, 0xaa, 0x3a, 0x0a, 0x0c, 0x53, 0x69, 0x72, 0x65, 0x6e, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x12, 0xab, 0x01, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x2f, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, + 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, + 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x37, 0x92, 0x41, 0x1a, 0x0a, 0x08, 0x50, + 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0e, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, 0x12, 0x2f, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, + 0x73, 0x12, 0xb4, 0x01, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x72, 0x12, 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x66, 0x92, 0x41, 0x63, 0x32, 0x61, 0x71, 0x75, 0x65, 0x72, - 0x79, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, 0x62, 0x61, 0x73, 0x65, 0x64, 0x20, 0x6f, - 0x6e, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, - 0x61, 0x62, 0x65, 0x6c, 0x73, 0x2e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, - 0x20, 0x69, 0x73, 0x20, 0x77, 0x72, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x20, 0x61, 0x73, 0x20, 0x6d, - 0x61, 0x70, 0x2e, 0x20, 0x65, 0x67, 0x2c, 0x20, 0x22, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x5b, - 0x6b, 0x65, 0x79, 0x31, 0x5d, 0x3d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x31, 0x22, 0x52, 0x06, 0x6c, - 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0xf7, 0x01, 0x0a, 0x12, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, - 0x65, 0x72, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x4a, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, - 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, - 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x7c, - 0x92, 0x41, 0x79, 0x32, 0x77, 0x71, 0x75, 0x65, 0x72, 0x79, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x20, 0x62, 0x61, 0x73, 0x65, 0x64, 0x20, 0x6f, 0x6e, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, - 0x76, 0x65, 0x72, 0x20, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x20, 0x73, 0x65, 0x6c, 0x65, - 0x63, 0x74, 0x6f, 0x72, 0x20, 0x69, 0x73, 0x20, 0x77, 0x72, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x20, - 0x61, 0x73, 0x20, 0x6d, 0x61, 0x70, 0x2e, 0x20, 0x65, 0x67, 0x2c, 0x20, 0x22, 0x72, 0x65, 0x63, - 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5b, 0x6b, - 0x65, 0x79, 0x31, 0x5d, 0x3d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x31, 0x22, 0x52, 0x11, 0x72, 0x65, - 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x1a, - 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x44, 0x0a, 0x16, 0x52, 0x65, - 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x22, 0xc7, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x53, 0x65, 0x6c, - 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x6e, 0x0a, 0x11, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, - 0x72, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x41, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, - 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x63, - 0x65, 0x69, 0x76, 0x65, 0x72, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x52, 0x65, - 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x10, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x53, 0x65, 0x6c, - 0x65, 0x63, 0x74, 0x6f, 0x72, 0x1a, 0x43, 0x0a, 0x15, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, - 0x72, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x9c, 0x04, 0x0a, 0x0c, 0x4e, - 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6e, - 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x12, - 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, - 0x70, 0x65, 0x12, 0x2b, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, - 0x4b, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x33, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, - 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x40, 0x0a, 0x0e, - 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x0d, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, - 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x37, 0x0a, 0x09, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x5f, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x5f, 0x6b, 0x65, - 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x4b, - 0x65, 0x79, 0x12, 0x5a, 0x0a, 0x12, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x73, - 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, - 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, - 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x65, 0x69, - 0x76, 0x65, 0x72, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x11, 0x72, 0x65, 0x63, - 0x65, 0x69, 0x76, 0x65, 0x72, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x1a, 0x39, - 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x6a, 0x0a, 0x19, 0x4c, 0x69, 0x73, - 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, - 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, - 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x32, 0xc5, 0x38, 0x0a, 0x0c, 0x53, 0x69, 0x72, 0x65, 0x6e, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xab, 0x01, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x50, - 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x2f, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, + 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, + 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3d, 0x92, 0x41, 0x1d, 0x0a, 0x08, + 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x20, 0x61, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x17, 0x3a, 0x01, 0x2a, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0xaa, 0x01, 0x0a, 0x0b, 0x47, 0x65, 0x74, + 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x2d, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, - 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, - 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, - 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x37, 0x92, 0x41, 0x1a, - 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0e, 0x6c, 0x69, 0x73, 0x74, - 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, - 0x12, 0x12, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x64, 0x65, 0x72, 0x73, 0x12, 0xb4, 0x01, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, - 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, + 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, - 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x76, - 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3d, 0x92, 0x41, - 0x1d, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x11, 0x63, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01, 0x2a, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0xaa, 0x01, 0x0a, 0x0b, - 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x2d, 0x2e, 0x67, 0x6f, - 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, - 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x76, 0x69, - 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x67, 0x6f, 0x74, + 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3c, 0x92, 0x41, 0x1a, 0x0a, 0x08, 0x50, 0x72, + 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0e, 0x67, 0x65, 0x74, 0x20, 0x61, 0x20, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x12, 0x17, 0x2f, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, + 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xb9, 0x01, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, + 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, + 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, + 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3c, 0x92, 0x41, 0x1a, 0x0a, - 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0e, 0x67, 0x65, 0x74, 0x20, 0x61, - 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x12, - 0x17, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xb9, 0x01, 0x0a, 0x0e, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x30, 0x2e, 0x67, 0x6f, - 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, - 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, - 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, + 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x42, 0x92, + 0x41, 0x1d, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x11, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x3a, 0x01, 0x2a, 0x1a, 0x17, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, + 0x7d, 0x12, 0xb6, 0x01, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x72, 0x12, 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, + 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, + 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3f, 0x92, 0x41, 0x1d, 0x0a, 0x08, + 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x11, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x20, 0x61, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x19, 0x2a, 0x17, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xcd, 0x01, 0x0a, 0x0e, 0x4e, + 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x12, 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, - 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x42, 0x92, 0x41, 0x1d, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, - 0x11, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x3a, 0x01, 0x2a, 0x1a, 0x17, 0x2f, 0x76, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x2f, - 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xb6, 0x01, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, - 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, - 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, - 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x76, - 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3f, 0x92, 0x41, - 0x1d, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x11, 0x64, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x20, 0x61, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x19, 0x2a, 0x17, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xcd, 0x01, - 0x0a, 0x0e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, - 0x12, 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, - 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4e, 0x6f, 0x74, - 0x69, 0x66, 0x79, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, - 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4e, - 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x56, 0x88, 0x02, 0x01, 0x92, 0x41, 0x29, 0x0a, 0x08, 0x52, - 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x12, 0x1d, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6e, 0x6f, - 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x72, 0x65, - 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, 0x3a, 0x01, 0x2a, 0x22, - 0x1c, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, - 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x2f, 0x73, 0x65, 0x6e, 0x64, 0x12, 0xb1, 0x01, - 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, - 0x12, 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, - 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, - 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3a, 0x92, 0x41, 0x1c, 0x0a, 0x09, 0x4e, 0x61, 0x6d, 0x65, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x0f, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x6e, 0x61, 0x6d, 0x65, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x73, 0x12, 0xba, 0x01, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, + 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, + 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, + 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, + 0x66, 0x79, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x56, 0x88, 0x02, 0x01, 0x92, 0x41, 0x29, 0x0a, 0x08, 0x52, 0x65, 0x63, 0x65, + 0x69, 0x76, 0x65, 0x72, 0x12, 0x1d, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, + 0x76, 0x65, 0x72, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, 0x3a, 0x01, 0x2a, 0x22, 0x1c, 0x2f, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x73, + 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x2f, 0x73, 0x65, 0x6e, 0x64, 0x12, 0xb1, 0x01, 0x0a, 0x0e, 0x4c, + 0x69, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0x30, 0x2e, + 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, + 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61, + 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, + 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x3a, 0x92, 0x41, 0x1c, 0x0a, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x12, 0x0f, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, 0x62, 0x65, + 0x74, 0x61, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0xba, + 0x01, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x12, 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, + 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, - 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, - 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x40, 0x92, 0x41, - 0x1f, 0x0a, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x3a, 0x01, 0x2a, 0x22, 0x13, 0x2f, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0xb0, - 0x01, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, - 0x2e, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, - 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4e, - 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x2f, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, - 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4e, - 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x3f, 0x92, 0x41, 0x1c, 0x0a, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x12, 0x0f, 0x67, 0x65, 0x74, 0x20, 0x61, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, - 0x7d, 0x12, 0xbf, 0x01, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x40, 0x92, 0x41, 0x1f, 0x0a, 0x09, + 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x20, 0x61, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x18, 0x3a, 0x01, 0x2a, 0x22, 0x13, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0xb0, 0x01, 0x0a, 0x0c, + 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x2e, 0x2e, 0x67, + 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, + 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x67, + 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, + 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3f, 0x92, + 0x41, 0x1c, 0x0a, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x0f, 0x67, + 0x65, 0x74, 0x20, 0x61, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x6e, + 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xbf, + 0x01, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x12, 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, + 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, - 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, - 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x45, 0x92, 0x41, - 0x1f, 0x0a, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x3a, 0x01, 0x2a, 0x1a, 0x18, 0x2f, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x2f, 0x7b, - 0x69, 0x64, 0x7d, 0x12, 0xbc, 0x01, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x61, - 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x45, 0x92, 0x41, 0x1f, 0x0a, 0x09, + 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x20, 0x61, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x1d, 0x3a, 0x01, 0x2a, 0x1a, 0x18, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, + 0x12, 0xbc, 0x01, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x12, 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, + 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x67, 0x6f, 0x74, + 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x42, 0x92, 0x41, 0x1f, + 0x0a, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x64, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x20, 0x61, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x2a, 0x18, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, + 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, + 0xc3, 0x01, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x33, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, + 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x61, 0x6d, - 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x42, - 0x92, 0x41, 0x1f, 0x0a, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, - 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x61, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x2a, 0x18, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, - 0x64, 0x7d, 0x12, 0xc3, 0x01, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x33, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x43, 0x92, 0x41, 0x22, 0x0a, 0x0c, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, 0x16, 0x2f, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0xcc, 0x01, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x2e, 0x67, + 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, + 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, + 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, + 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x49, 0x92, 0x41, 0x25, 0x0a, 0x0c, + 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x15, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x3a, 0x01, 0x2a, 0x22, 0x16, 0x2f, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x12, 0xc2, 0x01, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, - 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, - 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, - 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, - 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x43, 0x92, 0x41, 0x22, 0x0a, 0x0c, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x73, 0x75, 0x62, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, - 0x12, 0x16, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x73, 0x75, 0x62, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0xcc, 0x01, 0x0a, 0x12, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x34, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, - 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, - 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x49, 0x92, 0x41, - 0x25, 0x0a, 0x0c, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x3a, 0x01, 0x2a, 0x22, - 0x16, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0xc2, 0x01, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x53, - 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x31, 0x2e, 0x67, 0x6f, + 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, - 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, - 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, - 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x48, 0x92, 0x41, 0x22, 0x0a, 0x0c, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x47, 0x65, 0x74, 0x20, 0x61, 0x20, 0x73, 0x75, 0x62, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x12, - 0x1b, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xd1, 0x01, 0x0a, - 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, - 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x67, 0x6f, 0x74, 0x6f, - 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x4e, 0x92, 0x41, 0x25, 0x0a, 0x0c, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x73, 0x75, - 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, - 0x3a, 0x01, 0x2a, 0x1a, 0x1b, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x73, 0x75, - 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, - 0x12, 0xce, 0x01, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, - 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, - 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, - 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4b, 0x92, 0x41, 0x25, 0x0a, 0x0c, 0x53, 0x75, 0x62, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, - 0x61, 0x20, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x1d, 0x2a, 0x1b, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x73, - 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x7b, 0x69, 0x64, - 0x7d, 0x12, 0xab, 0x01, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, - 0x65, 0x72, 0x73, 0x12, 0x2f, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, - 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, - 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x37, 0x92, 0x41, 0x1a, 0x0a, 0x08, 0x52, 0x65, 0x63, - 0x65, 0x69, 0x76, 0x65, 0x72, 0x12, 0x0e, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x72, 0x65, 0x63, 0x65, - 0x69, 0x76, 0x65, 0x72, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, 0x12, 0x2f, 0x76, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x73, 0x12, - 0xb4, 0x01, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, - 0x65, 0x72, 0x12, 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, - 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, - 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3d, 0x92, 0x41, 0x1d, 0x0a, 0x08, 0x52, 0x65, - 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x12, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x61, - 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, - 0x01, 0x2a, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x72, 0x65, 0x63, - 0x65, 0x69, 0x76, 0x65, 0x72, 0x73, 0x12, 0xaa, 0x01, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x52, 0x65, - 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x12, 0x2d, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, - 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, - 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3c, 0x92, 0x41, 0x1a, 0x0a, 0x08, 0x52, 0x65, 0x63, 0x65, - 0x69, 0x76, 0x65, 0x72, 0x12, 0x0e, 0x67, 0x65, 0x74, 0x20, 0x61, 0x20, 0x72, 0x65, 0x63, 0x65, - 0x69, 0x76, 0x65, 0x72, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x12, 0x17, 0x2f, 0x76, 0x31, 0x62, - 0x65, 0x74, 0x61, 0x31, 0x2f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x73, 0x2f, 0x7b, - 0x69, 0x64, 0x7d, 0x12, 0xb9, 0x01, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, - 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x12, 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x48, 0x92, 0x41, 0x22, 0x0a, 0x0c, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x12, 0x47, 0x65, 0x74, 0x20, 0x61, 0x20, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x12, 0x1b, 0x2f, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xd1, 0x01, 0x0a, 0x12, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x34, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, + 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, - 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, - 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, - 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, - 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x42, 0x92, 0x41, 0x1d, - 0x0a, 0x08, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x12, 0x11, 0x75, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x20, 0x61, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x1c, 0x3a, 0x01, 0x2a, 0x1a, 0x17, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, - 0xb6, 0x01, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, - 0x65, 0x72, 0x12, 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, - 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, + 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4e, 0x92, + 0x41, 0x25, 0x0a, 0x0c, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x73, 0x75, 0x62, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x3a, 0x01, 0x2a, + 0x1a, 0x1b, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x73, 0x75, 0x62, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xce, 0x01, + 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3f, 0x92, 0x41, 0x1d, 0x0a, 0x08, 0x52, 0x65, - 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x12, 0x11, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x61, - 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x2a, - 0x17, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, - 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xb7, 0x01, 0x0a, 0x0a, 0x4c, 0x69, 0x73, - 0x74, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x12, 0x2c, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, - 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, - 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4c, 0x92, 0x41, 0x14, 0x0a, 0x05, 0x41, 0x6c, 0x65, 0x72, 0x74, - 0x12, 0x0b, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x2f, 0x12, 0x2d, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x61, 0x6c, - 0x65, 0x72, 0x74, 0x73, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x74, - 0x79, 0x70, 0x65, 0x7d, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x7d, 0x12, 0xc5, 0x01, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x6c, 0x65, - 0x72, 0x74, 0x73, 0x12, 0x2e, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, + 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x67, 0x6f, 0x74, + 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x4b, 0x92, 0x41, 0x25, 0x0a, 0x0c, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x61, 0x20, 0x73, + 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x1d, 0x2a, 0x1b, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x73, 0x75, 0x62, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xab, + 0x01, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x73, + 0x12, 0x2f, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, + 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, + 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x37, 0x92, 0x41, 0x1a, 0x0a, 0x08, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, + 0x65, 0x72, 0x12, 0x0e, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, + 0x72, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, 0x12, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x2f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x73, 0x12, 0xb4, 0x01, 0x0a, + 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x12, + 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, + 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, + 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3d, 0x92, 0x41, 0x1d, 0x0a, 0x08, 0x52, 0x65, 0x63, 0x65, 0x69, + 0x76, 0x65, 0x72, 0x12, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x72, 0x65, + 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01, 0x2a, 0x22, + 0x12, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, + 0x65, 0x72, 0x73, 0x12, 0xaa, 0x01, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, + 0x76, 0x65, 0x72, 0x12, 0x2d, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, + 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, + 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, + 0x65, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x3c, 0x92, 0x41, 0x1a, 0x0a, 0x08, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, + 0x72, 0x12, 0x0e, 0x67, 0x65, 0x74, 0x20, 0x61, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, + 0x72, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x12, 0x17, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, + 0x12, 0xb9, 0x01, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, + 0x76, 0x65, 0x72, 0x12, 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x54, 0x92, 0x41, 0x16, 0x0a, 0x05, 0x41, 0x6c, 0x65, 0x72, 0x74, - 0x12, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x35, 0x3a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0x2d, 0x2f, 0x76, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x2f, 0x7b, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x7d, 0x2f, 0x7b, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x8a, 0x02, 0x0a, 0x19, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x57, 0x69, 0x74, 0x68, 0x4e, - 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x3b, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, - 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, - 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x6c, 0x65, 0x72, 0x74, - 0x73, 0x57, 0x69, 0x74, 0x68, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3c, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, - 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x57, 0x69, - 0x74, 0x68, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x72, 0x92, 0x41, 0x25, 0x0a, 0x05, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x12, - 0x1c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x20, 0x77, - 0x69, 0x74, 0x68, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x44, 0x3a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0x3c, 0x2f, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2f, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x64, 0x65, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x7d, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x93, 0x01, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x2b, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, + 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x42, 0x92, 0x41, 0x1d, 0x0a, 0x08, 0x52, + 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x12, 0x11, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, + 0x61, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, + 0x3a, 0x01, 0x2a, 0x1a, 0x17, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x72, 0x65, + 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xb6, 0x01, 0x0a, + 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x12, + 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, + 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, + 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3f, 0x92, 0x41, 0x1d, 0x0a, 0x08, 0x52, 0x65, 0x63, 0x65, 0x69, + 0x76, 0x65, 0x72, 0x12, 0x11, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x61, 0x20, 0x72, 0x65, + 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x2a, 0x17, 0x2f, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x73, + 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xb7, 0x01, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, + 0x65, 0x72, 0x74, 0x73, 0x12, 0x2c, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, + 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x2b, 0x92, 0x41, 0x12, 0x0a, 0x04, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x0a, 0x6c, 0x69, 0x73, - 0x74, 0x20, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, - 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x12, 0xa0, 0x01, - 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x2c, 0x2e, 0x67, - 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, - 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, - 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x67, 0x6f, 0x74, - 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x75, 0x6c, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x92, 0x41, 0x19, 0x0a, 0x04, - 0x52, 0x75, 0x6c, 0x65, 0x12, 0x11, 0x61, 0x64, 0x64, 0x2f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x20, 0x61, 0x20, 0x72, 0x75, 0x6c, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x3a, 0x01, 0x2a, - 0x1a, 0x0e, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x72, 0x75, 0x6c, 0x65, 0x73, - 0x12, 0xab, 0x01, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, - 0x65, 0x73, 0x12, 0x2f, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, + 0x69, 0x73, 0x74, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x4c, 0x92, 0x41, 0x14, 0x0a, 0x05, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x12, 0x0b, 0x6c, + 0x69, 0x73, 0x74, 0x20, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2f, + 0x12, 0x2d, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x61, 0x6c, 0x65, 0x72, 0x74, + 0x73, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x7d, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x7d, 0x12, + 0xc5, 0x01, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, + 0x12, 0x2e, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, + 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x2f, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, + 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x54, 0x92, 0x41, 0x16, 0x0a, 0x05, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x12, 0x0d, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x35, 0x3a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0x2d, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x2f, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x64, 0x65, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x7d, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x8a, 0x02, 0x0a, 0x19, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x57, 0x69, 0x74, 0x68, 0x4e, 0x61, 0x6d, 0x65, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x3b, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, + 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x57, 0x69, + 0x74, 0x68, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x3c, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, + 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x57, 0x69, 0x74, 0x68, 0x4e, + 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x72, 0x92, 0x41, 0x25, 0x0a, 0x05, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x12, 0x1c, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x20, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x20, 0x77, 0x69, 0x74, 0x68, + 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x44, + 0x3a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0x3c, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0x2f, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x7d, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x93, 0x01, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x75, 0x6c, + 0x65, 0x73, 0x12, 0x2b, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, - 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x37, 0x92, 0x41, 0x1a, 0x0a, 0x08, 0x54, 0x65, 0x6d, 0x70, - 0x6c, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x74, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x74, 0x65, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, 0x12, 0x2f, 0x76, 0x31, 0x62, - 0x65, 0x74, 0x61, 0x31, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x12, 0xac, - 0x01, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x2d, - 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, - 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x65, - 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, - 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, - 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x65, 0x6d, - 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3e, 0x92, - 0x41, 0x1a, 0x0a, 0x08, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x67, 0x65, - 0x74, 0x20, 0x61, 0x20, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x1b, 0x12, 0x19, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x74, 0x65, 0x6d, - 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0xb8, 0x01, - 0x0a, 0x0e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, - 0x12, 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, - 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x73, - 0x65, 0x72, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, - 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, - 0x70, 0x73, 0x65, 0x72, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x41, 0x92, 0x41, 0x21, 0x0a, 0x08, 0x54, 0x65, 0x6d, 0x70, - 0x6c, 0x61, 0x74, 0x65, 0x12, 0x15, 0x61, 0x64, 0x64, 0x2f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x20, 0x61, 0x20, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x17, 0x3a, 0x01, 0x2a, 0x1a, 0x12, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x74, - 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x12, 0xb8, 0x01, 0x0a, 0x0e, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x30, 0x2e, 0x67, 0x6f, + 0x69, 0x73, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x2c, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, + 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x75, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x92, + 0x41, 0x12, 0x0a, 0x04, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x0a, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x72, + 0x75, 0x6c, 0x65, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x76, 0x31, 0x62, + 0x65, 0x74, 0x61, 0x31, 0x2f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x12, 0xa0, 0x01, 0x0a, 0x0a, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x2c, 0x2e, 0x67, 0x6f, 0x74, 0x6f, + 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x75, 0x6c, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, + 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, + 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x92, 0x41, 0x19, 0x0a, 0x04, 0x52, 0x75, 0x6c, + 0x65, 0x12, 0x11, 0x61, 0x64, 0x64, 0x2f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, + 0x72, 0x75, 0x6c, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x3a, 0x01, 0x2a, 0x1a, 0x0e, 0x2f, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x12, 0xab, 0x01, + 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x12, + 0x2f, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, + 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, + 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x37, 0x92, 0x41, 0x1a, 0x0a, 0x08, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x12, 0x0e, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, 0x12, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x12, 0xac, 0x01, 0x0a, 0x0b, + 0x47, 0x65, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x2d, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, - 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, - 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x67, 0x6f, 0x74, + 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3e, 0x92, 0x41, 0x1a, 0x0a, + 0x08, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x67, 0x65, 0x74, 0x20, 0x61, + 0x20, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x12, + 0x19, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x73, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0xb8, 0x01, 0x0a, 0x0e, 0x55, + 0x70, 0x73, 0x65, 0x72, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, - 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x41, 0x92, 0x41, 0x1d, 0x0a, 0x08, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, - 0x11, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x61, 0x20, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, - 0x74, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x2a, 0x19, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x2f, 0x7b, 0x6e, 0x61, - 0x6d, 0x65, 0x7d, 0x12, 0xc2, 0x01, 0x0a, 0x0e, 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x54, 0x65, - 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, - 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x2e, 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, + 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, + 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, + 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, + 0x72, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x41, 0x92, 0x41, 0x21, 0x0a, 0x08, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x12, 0x15, 0x61, 0x64, 0x64, 0x2f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, + 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01, + 0x2a, 0x1a, 0x12, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x73, 0x12, 0xb8, 0x01, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, - 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x54, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4b, 0x92, 0x41, 0x1d, - 0x0a, 0x08, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x11, 0x72, 0x65, 0x6e, 0x64, - 0x65, 0x72, 0x20, 0x61, 0x20, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x25, 0x3a, 0x01, 0x2a, 0x22, 0x20, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, - 0x7d, 0x2f, 0x72, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0xae, 0x01, 0x0a, 0x0d, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x2f, 0x2e, 0x67, 0x6f, 0x74, + 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x69, 0x6c, - 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x67, 0x6f, - 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, - 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x69, - 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3a, 0x92, - 0x41, 0x1b, 0x0a, 0x07, 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x10, 0x63, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x16, 0x3a, 0x01, 0x2a, 0x22, 0x11, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2f, 0x73, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0xa8, 0x01, 0x0a, 0x0c, 0x4c, 0x69, - 0x73, 0x74, 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x2e, 0x2e, 0x67, 0x6f, 0x74, - 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x69, 0x6c, 0x65, 0x6e, - 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x67, 0x6f, 0x74, - 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x69, 0x6c, 0x65, 0x6e, - 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x37, 0x92, 0x41, 0x1b, - 0x0a, 0x07, 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x10, 0x67, 0x65, 0x74, 0x20, 0x61, - 0x6c, 0x6c, 0x20, 0x73, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x13, 0x12, 0x11, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x73, 0x69, 0x6c, 0x65, - 0x6e, 0x63, 0x65, 0x73, 0x12, 0xa4, 0x01, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x53, 0x69, 0x6c, 0x65, - 0x6e, 0x63, 0x65, 0x12, 0x2c, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x6d, + 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x41, 0x92, + 0x41, 0x1d, 0x0a, 0x08, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x11, 0x64, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x20, 0x61, 0x20, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x2a, 0x19, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, + 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, + 0x12, 0xc2, 0x01, 0x0a, 0x0e, 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x54, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x12, 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x47, 0x65, 0x74, 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x2d, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, - 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, - 0x74, 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x39, 0x92, 0x41, 0x18, 0x0a, 0x07, 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x0d, - 0x67, 0x65, 0x74, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x18, 0x12, 0x16, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x73, 0x69, - 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xb0, 0x01, 0x0a, 0x0d, - 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x2f, 0x2e, - 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, - 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, - 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, + 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, + 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4b, 0x92, 0x41, 0x1d, 0x0a, 0x08, 0x54, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x11, 0x72, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x20, + 0x61, 0x20, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, + 0x3a, 0x01, 0x2a, 0x22, 0x20, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x74, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x72, + 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0xae, 0x01, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x2f, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, + 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, + 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x63, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, + 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, + 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x69, 0x6c, 0x65, 0x6e, + 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3a, 0x92, 0x41, 0x1b, 0x0a, + 0x07, 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x10, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x20, 0x61, 0x20, 0x73, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, + 0x3a, 0x01, 0x2a, 0x22, 0x11, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x73, 0x69, + 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0xa8, 0x01, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x53, + 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x2e, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, + 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, + 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, + 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, + 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x37, 0x92, 0x41, 0x1b, 0x0a, 0x07, 0x53, + 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x10, 0x67, 0x65, 0x74, 0x20, 0x61, 0x6c, 0x6c, 0x20, + 0x73, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, + 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x73, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, + 0x73, 0x12, 0xa4, 0x01, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, + 0x12, 0x2c, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, + 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, - 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, - 0x65, 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x3c, 0x92, 0x41, 0x1b, 0x0a, 0x07, 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x10, - 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x2a, 0x16, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2f, 0x73, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xcb, - 0x01, 0x0a, 0x10, 0x50, 0x6f, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, - 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, + 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x69, + 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x39, 0x92, + 0x41, 0x18, 0x0a, 0x07, 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x0d, 0x67, 0x65, 0x74, + 0x20, 0x61, 0x20, 0x73, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, + 0x12, 0x16, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x73, 0x69, 0x6c, 0x65, 0x6e, + 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xb0, 0x01, 0x0a, 0x0d, 0x45, 0x78, 0x70, + 0x69, 0x72, 0x65, 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x2f, 0x2e, 0x67, 0x6f, 0x74, + 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x53, 0x69, 0x6c, + 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x67, 0x6f, + 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x53, 0x69, + 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3c, 0x92, + 0x41, 0x1b, 0x0a, 0x07, 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x10, 0x65, 0x78, 0x70, + 0x69, 0x72, 0x65, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x18, 0x2a, 0x16, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x73, 0x69, + 0x6c, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xcb, 0x01, 0x0a, 0x10, 0x50, 0x6f, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, + 0x12, 0x32, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, + 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x50, 0x6f, 0x73, + 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, + 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4e, 0x92, 0x41, 0x2a, 0x0a, 0x0c, + 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x50, 0x6f, + 0x73, 0x74, 0x20, 0x61, 0x6e, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x3a, 0x01, + 0x2a, 0x22, 0x16, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x6e, 0x6f, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0xe2, 0x01, 0x0a, 0x15, 0x50, 0x6f, + 0x73, 0x74, 0x42, 0x75, 0x6c, 0x6b, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x12, 0x37, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, + 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, + 0x50, 0x6f, 0x73, 0x74, 0x42, 0x75, 0x6c, 0x6b, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x67, + 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, + 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x42, 0x75, 0x6c, + 0x6b, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x56, 0x92, 0x41, 0x2d, 0x0a, 0x0c, 0x4e, 0x6f, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x50, 0x6f, 0x73, 0x74, 0x20, + 0x62, 0x75, 0x6c, 0x6b, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x3a, 0x01, + 0x2a, 0x22, 0x1b, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x62, 0x75, 0x6c, 0x6b, + 0x2d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x88, + 0x02, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x12, 0x3a, 0x2e, 0x67, 0x6f, + 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4e, 0x92, 0x41, - 0x2a, 0x0a, 0x0c, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x1a, 0x50, 0x6f, 0x73, 0x74, 0x20, 0x61, 0x6e, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x20, 0x6e, - 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x1b, 0x3a, 0x01, 0x2a, 0x22, 0x16, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x6e, - 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x88, 0x02, 0x0a, - 0x18, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x12, 0x3a, 0x2e, 0x67, 0x6f, 0x74, 0x6f, - 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, - 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x73, 0x92, 0x41, 0x37, 0x0a, 0x0c, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x6d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x73, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x62, - 0x79, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x33, 0x12, 0x31, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x6e, - 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x7b, 0x6e, 0x6f, - 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x6d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x12, 0xc3, 0x01, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, - 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x33, 0x2e, - 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, - 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, - 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, - 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x43, 0x92, 0x41, 0x22, 0x0a, 0x0c, 0x4e, - 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x4c, 0x69, 0x73, - 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, 0x16, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, - 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0xc9, 0x01, - 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, - 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6e, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x42, 0x0e, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x50, 0x01, - 0x5a, 0x42, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x74, - 0x6f, 0x2f, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, - 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2f, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2f, - 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x3b, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x76, 0x31, 0x62, - 0x65, 0x74, 0x61, 0x31, 0x92, 0x41, 0x52, 0x12, 0x4d, 0x0a, 0x0a, 0x53, 0x69, 0x72, 0x65, 0x6e, - 0x20, 0x41, 0x50, 0x49, 0x73, 0x12, 0x3a, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x6f, 0x75, 0x72, 0x20, 0x53, 0x69, 0x72, 0x65, - 0x6e, 0x20, 0x41, 0x50, 0x49, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x67, 0x52, 0x50, 0x43, 0x20, - 0x61, 0x6e, 0x64, 0x0a, 0x67, 0x52, 0x50, 0x43, 0x2d, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, - 0x2e, 0x32, 0x03, 0x30, 0x2e, 0x36, 0x2a, 0x01, 0x01, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x73, 0x92, 0x41, 0x37, 0x0a, 0x0c, 0x4e, 0x6f, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, + 0x20, 0x62, 0x79, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x33, 0x12, 0x31, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0x2f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x7b, + 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x7d, + 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x12, 0xc3, 0x01, 0x0a, 0x11, 0x4c, 0x69, + 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, + 0x33, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x73, 0x69, + 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, + 0x6e, 0x79, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x43, 0x92, 0x41, 0x22, 0x0a, + 0x0c, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x4c, + 0x69, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, 0x16, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, + 0xc9, 0x01, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, + 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6e, 0x2e, 0x73, 0x69, 0x72, 0x65, 0x6e, + 0x42, 0x0e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, + 0x50, 0x01, 0x5a, 0x42, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, + 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, + 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2f, 0x73, 0x69, 0x72, 0x65, + 0x6e, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x3b, 0x73, 0x69, 0x72, 0x65, 0x6e, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x92, 0x41, 0x52, 0x12, 0x4d, 0x0a, 0x0a, 0x53, 0x69, 0x72, + 0x65, 0x6e, 0x20, 0x41, 0x50, 0x49, 0x73, 0x12, 0x3a, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x6f, 0x75, 0x72, 0x20, 0x53, 0x69, + 0x72, 0x65, 0x6e, 0x20, 0x41, 0x50, 0x49, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x67, 0x52, 0x50, + 0x43, 0x20, 0x61, 0x6e, 0x64, 0x0a, 0x67, 0x52, 0x50, 0x43, 0x2d, 0x47, 0x61, 0x74, 0x65, 0x77, + 0x61, 0x79, 0x2e, 0x32, 0x03, 0x30, 0x2e, 0x36, 0x2a, 0x01, 0x01, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -7281,7 +7401,7 @@ func file_gotocompany_siren_v1beta1_siren_proto_rawDescGZIP() []byte { return file_gotocompany_siren_v1beta1_siren_proto_rawDescData } -var file_gotocompany_siren_v1beta1_siren_proto_msgTypes = make([]protoimpl.MessageInfo, 118) +var file_gotocompany_siren_v1beta1_siren_proto_msgTypes = make([]protoimpl.MessageInfo, 120) var file_gotocompany_siren_v1beta1_siren_proto_goTypes = []interface{}{ (*Provider)(nil), // 0: gotocompany.siren.v1beta1.Provider (*ListProvidersRequest)(nil), // 1: gotocompany.siren.v1beta1.ListProvidersRequest @@ -7368,227 +7488,232 @@ var file_gotocompany_siren_v1beta1_siren_proto_goTypes = []interface{}{ (*ExpireSilenceResponse)(nil), // 82: gotocompany.siren.v1beta1.ExpireSilenceResponse (*PostNotificationRequest)(nil), // 83: gotocompany.siren.v1beta1.PostNotificationRequest (*PostNotificationResponse)(nil), // 84: gotocompany.siren.v1beta1.PostNotificationResponse - (*ListNotificationMessagesRequest)(nil), // 85: gotocompany.siren.v1beta1.ListNotificationMessagesRequest - (*NotificationMessage)(nil), // 86: gotocompany.siren.v1beta1.NotificationMessage - (*ListNotificationMessagesResponse)(nil), // 87: gotocompany.siren.v1beta1.ListNotificationMessagesResponse - (*ListNotificationsRequest)(nil), // 88: gotocompany.siren.v1beta1.ListNotificationsRequest - (*ReceiverSelector)(nil), // 89: gotocompany.siren.v1beta1.ReceiverSelector - (*Notification)(nil), // 90: gotocompany.siren.v1beta1.Notification - (*ListNotificationsResponse)(nil), // 91: gotocompany.siren.v1beta1.ListNotificationsResponse - nil, // 92: gotocompany.siren.v1beta1.Provider.LabelsEntry - nil, // 93: gotocompany.siren.v1beta1.CreateProviderRequest.LabelsEntry - nil, // 94: gotocompany.siren.v1beta1.UpdateProviderRequest.LabelsEntry - nil, // 95: gotocompany.siren.v1beta1.Namespace.LabelsEntry - nil, // 96: gotocompany.siren.v1beta1.CreateNamespaceRequest.LabelsEntry - nil, // 97: gotocompany.siren.v1beta1.UpdateNamespaceRequest.LabelsEntry - nil, // 98: gotocompany.siren.v1beta1.Subscription.MatchEntry - nil, // 99: gotocompany.siren.v1beta1.ListSubscriptionsRequest.MatchEntry - nil, // 100: gotocompany.siren.v1beta1.ListSubscriptionsRequest.NotificationMatchEntry - nil, // 101: gotocompany.siren.v1beta1.ListSubscriptionsRequest.MetadataEntry - nil, // 102: gotocompany.siren.v1beta1.CreateSubscriptionRequest.MatchEntry - nil, // 103: gotocompany.siren.v1beta1.UpdateSubscriptionRequest.MatchEntry - nil, // 104: gotocompany.siren.v1beta1.Receiver.LabelsEntry - nil, // 105: gotocompany.siren.v1beta1.ListReceiversRequest.LabelsEntry - nil, // 106: gotocompany.siren.v1beta1.CreateReceiverRequest.LabelsEntry - nil, // 107: gotocompany.siren.v1beta1.UpdateReceiverRequest.LabelsEntry - nil, // 108: gotocompany.siren.v1beta1.Alert.AnnotationsEntry - nil, // 109: gotocompany.siren.v1beta1.Alert.LabelsEntry - nil, // 110: gotocompany.siren.v1beta1.RenderTemplateRequest.VariablesEntry - nil, // 111: gotocompany.siren.v1beta1.ListSilencesRequest.MatchEntry - nil, // 112: gotocompany.siren.v1beta1.ListSilencesRequest.SubscriptionMatchEntry - nil, // 113: gotocompany.siren.v1beta1.PostNotificationRequest.LabelsEntry - nil, // 114: gotocompany.siren.v1beta1.ListNotificationsRequest.LabelsEntry - nil, // 115: gotocompany.siren.v1beta1.ListNotificationsRequest.ReceiverSelectorsEntry - nil, // 116: gotocompany.siren.v1beta1.ReceiverSelector.ReceiverSelectorEntry - nil, // 117: gotocompany.siren.v1beta1.Notification.LabelsEntry - (*structpb.Struct)(nil), // 118: google.protobuf.Struct - (*timestamppb.Timestamp)(nil), // 119: google.protobuf.Timestamp - (*durationpb.Duration)(nil), // 120: google.protobuf.Duration + (*PostBulkNotificationsRequest)(nil), // 85: gotocompany.siren.v1beta1.PostBulkNotificationsRequest + (*PostBulkNotificationsResponse)(nil), // 86: gotocompany.siren.v1beta1.PostBulkNotificationsResponse + (*ListNotificationMessagesRequest)(nil), // 87: gotocompany.siren.v1beta1.ListNotificationMessagesRequest + (*NotificationMessage)(nil), // 88: gotocompany.siren.v1beta1.NotificationMessage + (*ListNotificationMessagesResponse)(nil), // 89: gotocompany.siren.v1beta1.ListNotificationMessagesResponse + (*ListNotificationsRequest)(nil), // 90: gotocompany.siren.v1beta1.ListNotificationsRequest + (*ReceiverSelector)(nil), // 91: gotocompany.siren.v1beta1.ReceiverSelector + (*Notification)(nil), // 92: gotocompany.siren.v1beta1.Notification + (*ListNotificationsResponse)(nil), // 93: gotocompany.siren.v1beta1.ListNotificationsResponse + nil, // 94: gotocompany.siren.v1beta1.Provider.LabelsEntry + nil, // 95: gotocompany.siren.v1beta1.CreateProviderRequest.LabelsEntry + nil, // 96: gotocompany.siren.v1beta1.UpdateProviderRequest.LabelsEntry + nil, // 97: gotocompany.siren.v1beta1.Namespace.LabelsEntry + nil, // 98: gotocompany.siren.v1beta1.CreateNamespaceRequest.LabelsEntry + nil, // 99: gotocompany.siren.v1beta1.UpdateNamespaceRequest.LabelsEntry + nil, // 100: gotocompany.siren.v1beta1.Subscription.MatchEntry + nil, // 101: gotocompany.siren.v1beta1.ListSubscriptionsRequest.MatchEntry + nil, // 102: gotocompany.siren.v1beta1.ListSubscriptionsRequest.NotificationMatchEntry + nil, // 103: gotocompany.siren.v1beta1.ListSubscriptionsRequest.MetadataEntry + nil, // 104: gotocompany.siren.v1beta1.CreateSubscriptionRequest.MatchEntry + nil, // 105: gotocompany.siren.v1beta1.UpdateSubscriptionRequest.MatchEntry + nil, // 106: gotocompany.siren.v1beta1.Receiver.LabelsEntry + nil, // 107: gotocompany.siren.v1beta1.ListReceiversRequest.LabelsEntry + nil, // 108: gotocompany.siren.v1beta1.CreateReceiverRequest.LabelsEntry + nil, // 109: gotocompany.siren.v1beta1.UpdateReceiverRequest.LabelsEntry + nil, // 110: gotocompany.siren.v1beta1.Alert.AnnotationsEntry + nil, // 111: gotocompany.siren.v1beta1.Alert.LabelsEntry + nil, // 112: gotocompany.siren.v1beta1.RenderTemplateRequest.VariablesEntry + nil, // 113: gotocompany.siren.v1beta1.ListSilencesRequest.MatchEntry + nil, // 114: gotocompany.siren.v1beta1.ListSilencesRequest.SubscriptionMatchEntry + nil, // 115: gotocompany.siren.v1beta1.PostNotificationRequest.LabelsEntry + nil, // 116: gotocompany.siren.v1beta1.ListNotificationsRequest.LabelsEntry + nil, // 117: gotocompany.siren.v1beta1.ListNotificationsRequest.ReceiverSelectorsEntry + nil, // 118: gotocompany.siren.v1beta1.ReceiverSelector.ReceiverSelectorEntry + nil, // 119: gotocompany.siren.v1beta1.Notification.LabelsEntry + (*structpb.Struct)(nil), // 120: google.protobuf.Struct + (*timestamppb.Timestamp)(nil), // 121: google.protobuf.Timestamp + (*durationpb.Duration)(nil), // 122: google.protobuf.Duration } var file_gotocompany_siren_v1beta1_siren_proto_depIdxs = []int32{ - 118, // 0: gotocompany.siren.v1beta1.Provider.credentials:type_name -> google.protobuf.Struct - 92, // 1: gotocompany.siren.v1beta1.Provider.labels:type_name -> gotocompany.siren.v1beta1.Provider.LabelsEntry - 119, // 2: gotocompany.siren.v1beta1.Provider.created_at:type_name -> google.protobuf.Timestamp - 119, // 3: gotocompany.siren.v1beta1.Provider.updated_at:type_name -> google.protobuf.Timestamp + 120, // 0: gotocompany.siren.v1beta1.Provider.credentials:type_name -> google.protobuf.Struct + 94, // 1: gotocompany.siren.v1beta1.Provider.labels:type_name -> gotocompany.siren.v1beta1.Provider.LabelsEntry + 121, // 2: gotocompany.siren.v1beta1.Provider.created_at:type_name -> google.protobuf.Timestamp + 121, // 3: gotocompany.siren.v1beta1.Provider.updated_at:type_name -> google.protobuf.Timestamp 0, // 4: gotocompany.siren.v1beta1.ListProvidersResponse.providers:type_name -> gotocompany.siren.v1beta1.Provider - 118, // 5: gotocompany.siren.v1beta1.CreateProviderRequest.credentials:type_name -> google.protobuf.Struct - 93, // 6: gotocompany.siren.v1beta1.CreateProviderRequest.labels:type_name -> gotocompany.siren.v1beta1.CreateProviderRequest.LabelsEntry + 120, // 5: gotocompany.siren.v1beta1.CreateProviderRequest.credentials:type_name -> google.protobuf.Struct + 95, // 6: gotocompany.siren.v1beta1.CreateProviderRequest.labels:type_name -> gotocompany.siren.v1beta1.CreateProviderRequest.LabelsEntry 0, // 7: gotocompany.siren.v1beta1.GetProviderResponse.provider:type_name -> gotocompany.siren.v1beta1.Provider - 118, // 8: gotocompany.siren.v1beta1.UpdateProviderRequest.credentials:type_name -> google.protobuf.Struct - 94, // 9: gotocompany.siren.v1beta1.UpdateProviderRequest.labels:type_name -> gotocompany.siren.v1beta1.UpdateProviderRequest.LabelsEntry - 118, // 10: gotocompany.siren.v1beta1.Namespace.credentials:type_name -> google.protobuf.Struct - 95, // 11: gotocompany.siren.v1beta1.Namespace.labels:type_name -> gotocompany.siren.v1beta1.Namespace.LabelsEntry - 119, // 12: gotocompany.siren.v1beta1.Namespace.created_at:type_name -> google.protobuf.Timestamp - 119, // 13: gotocompany.siren.v1beta1.Namespace.updated_at:type_name -> google.protobuf.Timestamp + 120, // 8: gotocompany.siren.v1beta1.UpdateProviderRequest.credentials:type_name -> google.protobuf.Struct + 96, // 9: gotocompany.siren.v1beta1.UpdateProviderRequest.labels:type_name -> gotocompany.siren.v1beta1.UpdateProviderRequest.LabelsEntry + 120, // 10: gotocompany.siren.v1beta1.Namespace.credentials:type_name -> google.protobuf.Struct + 97, // 11: gotocompany.siren.v1beta1.Namespace.labels:type_name -> gotocompany.siren.v1beta1.Namespace.LabelsEntry + 121, // 12: gotocompany.siren.v1beta1.Namespace.created_at:type_name -> google.protobuf.Timestamp + 121, // 13: gotocompany.siren.v1beta1.Namespace.updated_at:type_name -> google.protobuf.Timestamp 11, // 14: gotocompany.siren.v1beta1.ListNamespacesResponse.namespaces:type_name -> gotocompany.siren.v1beta1.Namespace - 118, // 15: gotocompany.siren.v1beta1.CreateNamespaceRequest.credentials:type_name -> google.protobuf.Struct - 96, // 16: gotocompany.siren.v1beta1.CreateNamespaceRequest.labels:type_name -> gotocompany.siren.v1beta1.CreateNamespaceRequest.LabelsEntry - 119, // 17: gotocompany.siren.v1beta1.CreateNamespaceRequest.created_at:type_name -> google.protobuf.Timestamp - 119, // 18: gotocompany.siren.v1beta1.CreateNamespaceRequest.updated_at:type_name -> google.protobuf.Timestamp + 120, // 15: gotocompany.siren.v1beta1.CreateNamespaceRequest.credentials:type_name -> google.protobuf.Struct + 98, // 16: gotocompany.siren.v1beta1.CreateNamespaceRequest.labels:type_name -> gotocompany.siren.v1beta1.CreateNamespaceRequest.LabelsEntry + 121, // 17: gotocompany.siren.v1beta1.CreateNamespaceRequest.created_at:type_name -> google.protobuf.Timestamp + 121, // 18: gotocompany.siren.v1beta1.CreateNamespaceRequest.updated_at:type_name -> google.protobuf.Timestamp 11, // 19: gotocompany.siren.v1beta1.GetNamespaceResponse.namespace:type_name -> gotocompany.siren.v1beta1.Namespace - 118, // 20: gotocompany.siren.v1beta1.UpdateNamespaceRequest.credentials:type_name -> google.protobuf.Struct - 97, // 21: gotocompany.siren.v1beta1.UpdateNamespaceRequest.labels:type_name -> gotocompany.siren.v1beta1.UpdateNamespaceRequest.LabelsEntry - 118, // 22: gotocompany.siren.v1beta1.ReceiverMetadata.configuration:type_name -> google.protobuf.Struct + 120, // 20: gotocompany.siren.v1beta1.UpdateNamespaceRequest.credentials:type_name -> google.protobuf.Struct + 99, // 21: gotocompany.siren.v1beta1.UpdateNamespaceRequest.labels:type_name -> gotocompany.siren.v1beta1.UpdateNamespaceRequest.LabelsEntry + 120, // 22: gotocompany.siren.v1beta1.ReceiverMetadata.configuration:type_name -> google.protobuf.Struct 22, // 23: gotocompany.siren.v1beta1.Subscription.receivers:type_name -> gotocompany.siren.v1beta1.ReceiverMetadata - 98, // 24: gotocompany.siren.v1beta1.Subscription.match:type_name -> gotocompany.siren.v1beta1.Subscription.MatchEntry - 119, // 25: gotocompany.siren.v1beta1.Subscription.created_at:type_name -> google.protobuf.Timestamp - 119, // 26: gotocompany.siren.v1beta1.Subscription.updated_at:type_name -> google.protobuf.Timestamp - 118, // 27: gotocompany.siren.v1beta1.Subscription.metadata:type_name -> google.protobuf.Struct - 99, // 28: gotocompany.siren.v1beta1.ListSubscriptionsRequest.match:type_name -> gotocompany.siren.v1beta1.ListSubscriptionsRequest.MatchEntry - 100, // 29: gotocompany.siren.v1beta1.ListSubscriptionsRequest.notification_match:type_name -> gotocompany.siren.v1beta1.ListSubscriptionsRequest.NotificationMatchEntry - 101, // 30: gotocompany.siren.v1beta1.ListSubscriptionsRequest.metadata:type_name -> gotocompany.siren.v1beta1.ListSubscriptionsRequest.MetadataEntry + 100, // 24: gotocompany.siren.v1beta1.Subscription.match:type_name -> gotocompany.siren.v1beta1.Subscription.MatchEntry + 121, // 25: gotocompany.siren.v1beta1.Subscription.created_at:type_name -> google.protobuf.Timestamp + 121, // 26: gotocompany.siren.v1beta1.Subscription.updated_at:type_name -> google.protobuf.Timestamp + 120, // 27: gotocompany.siren.v1beta1.Subscription.metadata:type_name -> google.protobuf.Struct + 101, // 28: gotocompany.siren.v1beta1.ListSubscriptionsRequest.match:type_name -> gotocompany.siren.v1beta1.ListSubscriptionsRequest.MatchEntry + 102, // 29: gotocompany.siren.v1beta1.ListSubscriptionsRequest.notification_match:type_name -> gotocompany.siren.v1beta1.ListSubscriptionsRequest.NotificationMatchEntry + 103, // 30: gotocompany.siren.v1beta1.ListSubscriptionsRequest.metadata:type_name -> gotocompany.siren.v1beta1.ListSubscriptionsRequest.MetadataEntry 23, // 31: gotocompany.siren.v1beta1.ListSubscriptionsResponse.subscriptions:type_name -> gotocompany.siren.v1beta1.Subscription 22, // 32: gotocompany.siren.v1beta1.CreateSubscriptionRequest.receivers:type_name -> gotocompany.siren.v1beta1.ReceiverMetadata - 102, // 33: gotocompany.siren.v1beta1.CreateSubscriptionRequest.match:type_name -> gotocompany.siren.v1beta1.CreateSubscriptionRequest.MatchEntry - 118, // 34: gotocompany.siren.v1beta1.CreateSubscriptionRequest.metadata:type_name -> google.protobuf.Struct + 104, // 33: gotocompany.siren.v1beta1.CreateSubscriptionRequest.match:type_name -> gotocompany.siren.v1beta1.CreateSubscriptionRequest.MatchEntry + 120, // 34: gotocompany.siren.v1beta1.CreateSubscriptionRequest.metadata:type_name -> google.protobuf.Struct 23, // 35: gotocompany.siren.v1beta1.GetSubscriptionResponse.subscription:type_name -> gotocompany.siren.v1beta1.Subscription 22, // 36: gotocompany.siren.v1beta1.UpdateSubscriptionRequest.receivers:type_name -> gotocompany.siren.v1beta1.ReceiverMetadata - 103, // 37: gotocompany.siren.v1beta1.UpdateSubscriptionRequest.match:type_name -> gotocompany.siren.v1beta1.UpdateSubscriptionRequest.MatchEntry - 118, // 38: gotocompany.siren.v1beta1.UpdateSubscriptionRequest.metadata:type_name -> google.protobuf.Struct - 104, // 39: gotocompany.siren.v1beta1.Receiver.labels:type_name -> gotocompany.siren.v1beta1.Receiver.LabelsEntry - 118, // 40: gotocompany.siren.v1beta1.Receiver.configurations:type_name -> google.protobuf.Struct - 118, // 41: gotocompany.siren.v1beta1.Receiver.data:type_name -> google.protobuf.Struct - 119, // 42: gotocompany.siren.v1beta1.Receiver.created_at:type_name -> google.protobuf.Timestamp - 119, // 43: gotocompany.siren.v1beta1.Receiver.updated_at:type_name -> google.protobuf.Timestamp - 105, // 44: gotocompany.siren.v1beta1.ListReceiversRequest.labels:type_name -> gotocompany.siren.v1beta1.ListReceiversRequest.LabelsEntry + 105, // 37: gotocompany.siren.v1beta1.UpdateSubscriptionRequest.match:type_name -> gotocompany.siren.v1beta1.UpdateSubscriptionRequest.MatchEntry + 120, // 38: gotocompany.siren.v1beta1.UpdateSubscriptionRequest.metadata:type_name -> google.protobuf.Struct + 106, // 39: gotocompany.siren.v1beta1.Receiver.labels:type_name -> gotocompany.siren.v1beta1.Receiver.LabelsEntry + 120, // 40: gotocompany.siren.v1beta1.Receiver.configurations:type_name -> google.protobuf.Struct + 120, // 41: gotocompany.siren.v1beta1.Receiver.data:type_name -> google.protobuf.Struct + 121, // 42: gotocompany.siren.v1beta1.Receiver.created_at:type_name -> google.protobuf.Timestamp + 121, // 43: gotocompany.siren.v1beta1.Receiver.updated_at:type_name -> google.protobuf.Timestamp + 107, // 44: gotocompany.siren.v1beta1.ListReceiversRequest.labels:type_name -> gotocompany.siren.v1beta1.ListReceiversRequest.LabelsEntry 34, // 45: gotocompany.siren.v1beta1.ListReceiversResponse.receivers:type_name -> gotocompany.siren.v1beta1.Receiver - 106, // 46: gotocompany.siren.v1beta1.CreateReceiverRequest.labels:type_name -> gotocompany.siren.v1beta1.CreateReceiverRequest.LabelsEntry - 118, // 47: gotocompany.siren.v1beta1.CreateReceiverRequest.configurations:type_name -> google.protobuf.Struct + 108, // 46: gotocompany.siren.v1beta1.CreateReceiverRequest.labels:type_name -> gotocompany.siren.v1beta1.CreateReceiverRequest.LabelsEntry + 120, // 47: gotocompany.siren.v1beta1.CreateReceiverRequest.configurations:type_name -> google.protobuf.Struct 34, // 48: gotocompany.siren.v1beta1.GetReceiverResponse.receiver:type_name -> gotocompany.siren.v1beta1.Receiver - 107, // 49: gotocompany.siren.v1beta1.UpdateReceiverRequest.labels:type_name -> gotocompany.siren.v1beta1.UpdateReceiverRequest.LabelsEntry - 118, // 50: gotocompany.siren.v1beta1.UpdateReceiverRequest.configurations:type_name -> google.protobuf.Struct - 118, // 51: gotocompany.siren.v1beta1.NotifyReceiverRequest.payload:type_name -> google.protobuf.Struct - 119, // 52: gotocompany.siren.v1beta1.Alert.triggered_at:type_name -> google.protobuf.Timestamp - 119, // 53: gotocompany.siren.v1beta1.Alert.created_at:type_name -> google.protobuf.Timestamp - 119, // 54: gotocompany.siren.v1beta1.Alert.updated_at:type_name -> google.protobuf.Timestamp - 108, // 55: gotocompany.siren.v1beta1.Alert.annotations:type_name -> gotocompany.siren.v1beta1.Alert.AnnotationsEntry - 109, // 56: gotocompany.siren.v1beta1.Alert.labels:type_name -> gotocompany.siren.v1beta1.Alert.LabelsEntry + 109, // 49: gotocompany.siren.v1beta1.UpdateReceiverRequest.labels:type_name -> gotocompany.siren.v1beta1.UpdateReceiverRequest.LabelsEntry + 120, // 50: gotocompany.siren.v1beta1.UpdateReceiverRequest.configurations:type_name -> google.protobuf.Struct + 120, // 51: gotocompany.siren.v1beta1.NotifyReceiverRequest.payload:type_name -> google.protobuf.Struct + 121, // 52: gotocompany.siren.v1beta1.Alert.triggered_at:type_name -> google.protobuf.Timestamp + 121, // 53: gotocompany.siren.v1beta1.Alert.created_at:type_name -> google.protobuf.Timestamp + 121, // 54: gotocompany.siren.v1beta1.Alert.updated_at:type_name -> google.protobuf.Timestamp + 110, // 55: gotocompany.siren.v1beta1.Alert.annotations:type_name -> gotocompany.siren.v1beta1.Alert.AnnotationsEntry + 111, // 56: gotocompany.siren.v1beta1.Alert.labels:type_name -> gotocompany.siren.v1beta1.Alert.LabelsEntry 47, // 57: gotocompany.siren.v1beta1.ListAlertsResponse.alerts:type_name -> gotocompany.siren.v1beta1.Alert - 118, // 58: gotocompany.siren.v1beta1.CreateAlertsRequest.body:type_name -> google.protobuf.Struct + 120, // 58: gotocompany.siren.v1beta1.CreateAlertsRequest.body:type_name -> google.protobuf.Struct 47, // 59: gotocompany.siren.v1beta1.CreateAlertsResponse.alerts:type_name -> gotocompany.siren.v1beta1.Alert - 118, // 60: gotocompany.siren.v1beta1.CreateAlertsWithNamespaceRequest.body:type_name -> google.protobuf.Struct + 120, // 60: gotocompany.siren.v1beta1.CreateAlertsWithNamespaceRequest.body:type_name -> google.protobuf.Struct 47, // 61: gotocompany.siren.v1beta1.CreateAlertsWithNamespaceResponse.alerts:type_name -> gotocompany.siren.v1beta1.Alert 57, // 62: gotocompany.siren.v1beta1.Rule.variables:type_name -> gotocompany.siren.v1beta1.Variables - 119, // 63: gotocompany.siren.v1beta1.Rule.created_at:type_name -> google.protobuf.Timestamp - 119, // 64: gotocompany.siren.v1beta1.Rule.updated_at:type_name -> google.protobuf.Timestamp + 121, // 63: gotocompany.siren.v1beta1.Rule.created_at:type_name -> google.protobuf.Timestamp + 121, // 64: gotocompany.siren.v1beta1.Rule.updated_at:type_name -> google.protobuf.Timestamp 56, // 65: gotocompany.siren.v1beta1.ListRulesResponse.rules:type_name -> gotocompany.siren.v1beta1.Rule 57, // 66: gotocompany.siren.v1beta1.UpdateRuleRequest.variables:type_name -> gotocompany.siren.v1beta1.Variables 56, // 67: gotocompany.siren.v1beta1.UpdateRuleResponse.rule:type_name -> gotocompany.siren.v1beta1.Rule - 119, // 68: gotocompany.siren.v1beta1.Template.created_at:type_name -> google.protobuf.Timestamp - 119, // 69: gotocompany.siren.v1beta1.Template.updated_at:type_name -> google.protobuf.Timestamp + 121, // 68: gotocompany.siren.v1beta1.Template.created_at:type_name -> google.protobuf.Timestamp + 121, // 69: gotocompany.siren.v1beta1.Template.updated_at:type_name -> google.protobuf.Timestamp 62, // 70: gotocompany.siren.v1beta1.Template.variables:type_name -> gotocompany.siren.v1beta1.TemplateVariables 63, // 71: gotocompany.siren.v1beta1.ListTemplatesResponse.templates:type_name -> gotocompany.siren.v1beta1.Template 62, // 72: gotocompany.siren.v1beta1.UpsertTemplateRequest.variables:type_name -> gotocompany.siren.v1beta1.TemplateVariables 63, // 73: gotocompany.siren.v1beta1.GetTemplateResponse.template:type_name -> gotocompany.siren.v1beta1.Template - 110, // 74: gotocompany.siren.v1beta1.RenderTemplateRequest.variables:type_name -> gotocompany.siren.v1beta1.RenderTemplateRequest.VariablesEntry - 118, // 75: gotocompany.siren.v1beta1.Silence.target_expression:type_name -> google.protobuf.Struct - 119, // 76: gotocompany.siren.v1beta1.Silence.created_at:type_name -> google.protobuf.Timestamp - 119, // 77: gotocompany.siren.v1beta1.Silence.updated_at:type_name -> google.protobuf.Timestamp - 119, // 78: gotocompany.siren.v1beta1.Silence.deleted_at:type_name -> google.protobuf.Timestamp - 118, // 79: gotocompany.siren.v1beta1.CreateSilenceRequest.target_expression:type_name -> google.protobuf.Struct - 111, // 80: gotocompany.siren.v1beta1.ListSilencesRequest.match:type_name -> gotocompany.siren.v1beta1.ListSilencesRequest.MatchEntry - 112, // 81: gotocompany.siren.v1beta1.ListSilencesRequest.subscription_match:type_name -> gotocompany.siren.v1beta1.ListSilencesRequest.SubscriptionMatchEntry + 112, // 74: gotocompany.siren.v1beta1.RenderTemplateRequest.variables:type_name -> gotocompany.siren.v1beta1.RenderTemplateRequest.VariablesEntry + 120, // 75: gotocompany.siren.v1beta1.Silence.target_expression:type_name -> google.protobuf.Struct + 121, // 76: gotocompany.siren.v1beta1.Silence.created_at:type_name -> google.protobuf.Timestamp + 121, // 77: gotocompany.siren.v1beta1.Silence.updated_at:type_name -> google.protobuf.Timestamp + 121, // 78: gotocompany.siren.v1beta1.Silence.deleted_at:type_name -> google.protobuf.Timestamp + 120, // 79: gotocompany.siren.v1beta1.CreateSilenceRequest.target_expression:type_name -> google.protobuf.Struct + 113, // 80: gotocompany.siren.v1beta1.ListSilencesRequest.match:type_name -> gotocompany.siren.v1beta1.ListSilencesRequest.MatchEntry + 114, // 81: gotocompany.siren.v1beta1.ListSilencesRequest.subscription_match:type_name -> gotocompany.siren.v1beta1.ListSilencesRequest.SubscriptionMatchEntry 74, // 82: gotocompany.siren.v1beta1.ListSilencesResponse.silences:type_name -> gotocompany.siren.v1beta1.Silence 74, // 83: gotocompany.siren.v1beta1.GetSilenceResponse.silence:type_name -> gotocompany.siren.v1beta1.Silence - 118, // 84: gotocompany.siren.v1beta1.PostNotificationRequest.receivers:type_name -> google.protobuf.Struct - 118, // 85: gotocompany.siren.v1beta1.PostNotificationRequest.data:type_name -> google.protobuf.Struct - 113, // 86: gotocompany.siren.v1beta1.PostNotificationRequest.labels:type_name -> gotocompany.siren.v1beta1.PostNotificationRequest.LabelsEntry - 118, // 87: gotocompany.siren.v1beta1.NotificationMessage.details:type_name -> google.protobuf.Struct - 119, // 88: gotocompany.siren.v1beta1.NotificationMessage.expired_at:type_name -> google.protobuf.Timestamp - 119, // 89: gotocompany.siren.v1beta1.NotificationMessage.created_at:type_name -> google.protobuf.Timestamp - 119, // 90: gotocompany.siren.v1beta1.NotificationMessage.updated_at:type_name -> google.protobuf.Timestamp - 118, // 91: gotocompany.siren.v1beta1.NotificationMessage.configs:type_name -> google.protobuf.Struct - 86, // 92: gotocompany.siren.v1beta1.ListNotificationMessagesResponse.messages:type_name -> gotocompany.siren.v1beta1.NotificationMessage - 114, // 93: gotocompany.siren.v1beta1.ListNotificationsRequest.labels:type_name -> gotocompany.siren.v1beta1.ListNotificationsRequest.LabelsEntry - 115, // 94: gotocompany.siren.v1beta1.ListNotificationsRequest.receiver_selectors:type_name -> gotocompany.siren.v1beta1.ListNotificationsRequest.ReceiverSelectorsEntry - 116, // 95: gotocompany.siren.v1beta1.ReceiverSelector.receiver_selector:type_name -> gotocompany.siren.v1beta1.ReceiverSelector.ReceiverSelectorEntry - 118, // 96: gotocompany.siren.v1beta1.Notification.data:type_name -> google.protobuf.Struct - 117, // 97: gotocompany.siren.v1beta1.Notification.labels:type_name -> gotocompany.siren.v1beta1.Notification.LabelsEntry - 120, // 98: gotocompany.siren.v1beta1.Notification.valid_duration:type_name -> google.protobuf.Duration - 119, // 99: gotocompany.siren.v1beta1.Notification.create_at:type_name -> google.protobuf.Timestamp - 89, // 100: gotocompany.siren.v1beta1.Notification.receiver_selectors:type_name -> gotocompany.siren.v1beta1.ReceiverSelector - 90, // 101: gotocompany.siren.v1beta1.ListNotificationsResponse.notifications:type_name -> gotocompany.siren.v1beta1.Notification - 1, // 102: gotocompany.siren.v1beta1.SirenService.ListProviders:input_type -> gotocompany.siren.v1beta1.ListProvidersRequest - 3, // 103: gotocompany.siren.v1beta1.SirenService.CreateProvider:input_type -> gotocompany.siren.v1beta1.CreateProviderRequest - 5, // 104: gotocompany.siren.v1beta1.SirenService.GetProvider:input_type -> gotocompany.siren.v1beta1.GetProviderRequest - 7, // 105: gotocompany.siren.v1beta1.SirenService.UpdateProvider:input_type -> gotocompany.siren.v1beta1.UpdateProviderRequest - 9, // 106: gotocompany.siren.v1beta1.SirenService.DeleteProvider:input_type -> gotocompany.siren.v1beta1.DeleteProviderRequest - 45, // 107: gotocompany.siren.v1beta1.SirenService.NotifyReceiver:input_type -> gotocompany.siren.v1beta1.NotifyReceiverRequest - 12, // 108: gotocompany.siren.v1beta1.SirenService.ListNamespaces:input_type -> gotocompany.siren.v1beta1.ListNamespacesRequest - 14, // 109: gotocompany.siren.v1beta1.SirenService.CreateNamespace:input_type -> gotocompany.siren.v1beta1.CreateNamespaceRequest - 16, // 110: gotocompany.siren.v1beta1.SirenService.GetNamespace:input_type -> gotocompany.siren.v1beta1.GetNamespaceRequest - 18, // 111: gotocompany.siren.v1beta1.SirenService.UpdateNamespace:input_type -> gotocompany.siren.v1beta1.UpdateNamespaceRequest - 20, // 112: gotocompany.siren.v1beta1.SirenService.DeleteNamespace:input_type -> gotocompany.siren.v1beta1.DeleteNamespaceRequest - 24, // 113: gotocompany.siren.v1beta1.SirenService.ListSubscriptions:input_type -> gotocompany.siren.v1beta1.ListSubscriptionsRequest - 26, // 114: gotocompany.siren.v1beta1.SirenService.CreateSubscription:input_type -> gotocompany.siren.v1beta1.CreateSubscriptionRequest - 28, // 115: gotocompany.siren.v1beta1.SirenService.GetSubscription:input_type -> gotocompany.siren.v1beta1.GetSubscriptionRequest - 30, // 116: gotocompany.siren.v1beta1.SirenService.UpdateSubscription:input_type -> gotocompany.siren.v1beta1.UpdateSubscriptionRequest - 32, // 117: gotocompany.siren.v1beta1.SirenService.DeleteSubscription:input_type -> gotocompany.siren.v1beta1.DeleteSubscriptionRequest - 35, // 118: gotocompany.siren.v1beta1.SirenService.ListReceivers:input_type -> gotocompany.siren.v1beta1.ListReceiversRequest - 37, // 119: gotocompany.siren.v1beta1.SirenService.CreateReceiver:input_type -> gotocompany.siren.v1beta1.CreateReceiverRequest - 39, // 120: gotocompany.siren.v1beta1.SirenService.GetReceiver:input_type -> gotocompany.siren.v1beta1.GetReceiverRequest - 41, // 121: gotocompany.siren.v1beta1.SirenService.UpdateReceiver:input_type -> gotocompany.siren.v1beta1.UpdateReceiverRequest - 43, // 122: gotocompany.siren.v1beta1.SirenService.DeleteReceiver:input_type -> gotocompany.siren.v1beta1.DeleteReceiverRequest - 48, // 123: gotocompany.siren.v1beta1.SirenService.ListAlerts:input_type -> gotocompany.siren.v1beta1.ListAlertsRequest - 50, // 124: gotocompany.siren.v1beta1.SirenService.CreateAlerts:input_type -> gotocompany.siren.v1beta1.CreateAlertsRequest - 52, // 125: gotocompany.siren.v1beta1.SirenService.CreateAlertsWithNamespace:input_type -> gotocompany.siren.v1beta1.CreateAlertsWithNamespaceRequest - 58, // 126: gotocompany.siren.v1beta1.SirenService.ListRules:input_type -> gotocompany.siren.v1beta1.ListRulesRequest - 60, // 127: gotocompany.siren.v1beta1.SirenService.UpdateRule:input_type -> gotocompany.siren.v1beta1.UpdateRuleRequest - 64, // 128: gotocompany.siren.v1beta1.SirenService.ListTemplates:input_type -> gotocompany.siren.v1beta1.ListTemplatesRequest - 68, // 129: gotocompany.siren.v1beta1.SirenService.GetTemplate:input_type -> gotocompany.siren.v1beta1.GetTemplateRequest - 66, // 130: gotocompany.siren.v1beta1.SirenService.UpsertTemplate:input_type -> gotocompany.siren.v1beta1.UpsertTemplateRequest - 70, // 131: gotocompany.siren.v1beta1.SirenService.DeleteTemplate:input_type -> gotocompany.siren.v1beta1.DeleteTemplateRequest - 72, // 132: gotocompany.siren.v1beta1.SirenService.RenderTemplate:input_type -> gotocompany.siren.v1beta1.RenderTemplateRequest - 75, // 133: gotocompany.siren.v1beta1.SirenService.CreateSilence:input_type -> gotocompany.siren.v1beta1.CreateSilenceRequest - 77, // 134: gotocompany.siren.v1beta1.SirenService.ListSilences:input_type -> gotocompany.siren.v1beta1.ListSilencesRequest - 79, // 135: gotocompany.siren.v1beta1.SirenService.GetSilence:input_type -> gotocompany.siren.v1beta1.GetSilenceRequest - 81, // 136: gotocompany.siren.v1beta1.SirenService.ExpireSilence:input_type -> gotocompany.siren.v1beta1.ExpireSilenceRequest - 83, // 137: gotocompany.siren.v1beta1.SirenService.PostNotification:input_type -> gotocompany.siren.v1beta1.PostNotificationRequest - 85, // 138: gotocompany.siren.v1beta1.SirenService.ListNotificationMessages:input_type -> gotocompany.siren.v1beta1.ListNotificationMessagesRequest - 88, // 139: gotocompany.siren.v1beta1.SirenService.ListNotifications:input_type -> gotocompany.siren.v1beta1.ListNotificationsRequest - 2, // 140: gotocompany.siren.v1beta1.SirenService.ListProviders:output_type -> gotocompany.siren.v1beta1.ListProvidersResponse - 4, // 141: gotocompany.siren.v1beta1.SirenService.CreateProvider:output_type -> gotocompany.siren.v1beta1.CreateProviderResponse - 6, // 142: gotocompany.siren.v1beta1.SirenService.GetProvider:output_type -> gotocompany.siren.v1beta1.GetProviderResponse - 8, // 143: gotocompany.siren.v1beta1.SirenService.UpdateProvider:output_type -> gotocompany.siren.v1beta1.UpdateProviderResponse - 10, // 144: gotocompany.siren.v1beta1.SirenService.DeleteProvider:output_type -> gotocompany.siren.v1beta1.DeleteProviderResponse - 46, // 145: gotocompany.siren.v1beta1.SirenService.NotifyReceiver:output_type -> gotocompany.siren.v1beta1.NotifyReceiverResponse - 13, // 146: gotocompany.siren.v1beta1.SirenService.ListNamespaces:output_type -> gotocompany.siren.v1beta1.ListNamespacesResponse - 15, // 147: gotocompany.siren.v1beta1.SirenService.CreateNamespace:output_type -> gotocompany.siren.v1beta1.CreateNamespaceResponse - 17, // 148: gotocompany.siren.v1beta1.SirenService.GetNamespace:output_type -> gotocompany.siren.v1beta1.GetNamespaceResponse - 19, // 149: gotocompany.siren.v1beta1.SirenService.UpdateNamespace:output_type -> gotocompany.siren.v1beta1.UpdateNamespaceResponse - 21, // 150: gotocompany.siren.v1beta1.SirenService.DeleteNamespace:output_type -> gotocompany.siren.v1beta1.DeleteNamespaceResponse - 25, // 151: gotocompany.siren.v1beta1.SirenService.ListSubscriptions:output_type -> gotocompany.siren.v1beta1.ListSubscriptionsResponse - 27, // 152: gotocompany.siren.v1beta1.SirenService.CreateSubscription:output_type -> gotocompany.siren.v1beta1.CreateSubscriptionResponse - 29, // 153: gotocompany.siren.v1beta1.SirenService.GetSubscription:output_type -> gotocompany.siren.v1beta1.GetSubscriptionResponse - 31, // 154: gotocompany.siren.v1beta1.SirenService.UpdateSubscription:output_type -> gotocompany.siren.v1beta1.UpdateSubscriptionResponse - 33, // 155: gotocompany.siren.v1beta1.SirenService.DeleteSubscription:output_type -> gotocompany.siren.v1beta1.DeleteSubscriptionResponse - 36, // 156: gotocompany.siren.v1beta1.SirenService.ListReceivers:output_type -> gotocompany.siren.v1beta1.ListReceiversResponse - 38, // 157: gotocompany.siren.v1beta1.SirenService.CreateReceiver:output_type -> gotocompany.siren.v1beta1.CreateReceiverResponse - 40, // 158: gotocompany.siren.v1beta1.SirenService.GetReceiver:output_type -> gotocompany.siren.v1beta1.GetReceiverResponse - 42, // 159: gotocompany.siren.v1beta1.SirenService.UpdateReceiver:output_type -> gotocompany.siren.v1beta1.UpdateReceiverResponse - 44, // 160: gotocompany.siren.v1beta1.SirenService.DeleteReceiver:output_type -> gotocompany.siren.v1beta1.DeleteReceiverResponse - 49, // 161: gotocompany.siren.v1beta1.SirenService.ListAlerts:output_type -> gotocompany.siren.v1beta1.ListAlertsResponse - 51, // 162: gotocompany.siren.v1beta1.SirenService.CreateAlerts:output_type -> gotocompany.siren.v1beta1.CreateAlertsResponse - 53, // 163: gotocompany.siren.v1beta1.SirenService.CreateAlertsWithNamespace:output_type -> gotocompany.siren.v1beta1.CreateAlertsWithNamespaceResponse - 59, // 164: gotocompany.siren.v1beta1.SirenService.ListRules:output_type -> gotocompany.siren.v1beta1.ListRulesResponse - 61, // 165: gotocompany.siren.v1beta1.SirenService.UpdateRule:output_type -> gotocompany.siren.v1beta1.UpdateRuleResponse - 65, // 166: gotocompany.siren.v1beta1.SirenService.ListTemplates:output_type -> gotocompany.siren.v1beta1.ListTemplatesResponse - 69, // 167: gotocompany.siren.v1beta1.SirenService.GetTemplate:output_type -> gotocompany.siren.v1beta1.GetTemplateResponse - 67, // 168: gotocompany.siren.v1beta1.SirenService.UpsertTemplate:output_type -> gotocompany.siren.v1beta1.UpsertTemplateResponse - 71, // 169: gotocompany.siren.v1beta1.SirenService.DeleteTemplate:output_type -> gotocompany.siren.v1beta1.DeleteTemplateResponse - 73, // 170: gotocompany.siren.v1beta1.SirenService.RenderTemplate:output_type -> gotocompany.siren.v1beta1.RenderTemplateResponse - 76, // 171: gotocompany.siren.v1beta1.SirenService.CreateSilence:output_type -> gotocompany.siren.v1beta1.CreateSilenceResponse - 78, // 172: gotocompany.siren.v1beta1.SirenService.ListSilences:output_type -> gotocompany.siren.v1beta1.ListSilencesResponse - 80, // 173: gotocompany.siren.v1beta1.SirenService.GetSilence:output_type -> gotocompany.siren.v1beta1.GetSilenceResponse - 82, // 174: gotocompany.siren.v1beta1.SirenService.ExpireSilence:output_type -> gotocompany.siren.v1beta1.ExpireSilenceResponse - 84, // 175: gotocompany.siren.v1beta1.SirenService.PostNotification:output_type -> gotocompany.siren.v1beta1.PostNotificationResponse - 87, // 176: gotocompany.siren.v1beta1.SirenService.ListNotificationMessages:output_type -> gotocompany.siren.v1beta1.ListNotificationMessagesResponse - 91, // 177: gotocompany.siren.v1beta1.SirenService.ListNotifications:output_type -> gotocompany.siren.v1beta1.ListNotificationsResponse - 140, // [140:178] is the sub-list for method output_type - 102, // [102:140] is the sub-list for method input_type - 102, // [102:102] is the sub-list for extension type_name - 102, // [102:102] is the sub-list for extension extendee - 0, // [0:102] is the sub-list for field type_name + 120, // 84: gotocompany.siren.v1beta1.PostNotificationRequest.receivers:type_name -> google.protobuf.Struct + 120, // 85: gotocompany.siren.v1beta1.PostNotificationRequest.data:type_name -> google.protobuf.Struct + 115, // 86: gotocompany.siren.v1beta1.PostNotificationRequest.labels:type_name -> gotocompany.siren.v1beta1.PostNotificationRequest.LabelsEntry + 92, // 87: gotocompany.siren.v1beta1.PostBulkNotificationsRequest.notifications:type_name -> gotocompany.siren.v1beta1.Notification + 120, // 88: gotocompany.siren.v1beta1.NotificationMessage.details:type_name -> google.protobuf.Struct + 121, // 89: gotocompany.siren.v1beta1.NotificationMessage.expired_at:type_name -> google.protobuf.Timestamp + 121, // 90: gotocompany.siren.v1beta1.NotificationMessage.created_at:type_name -> google.protobuf.Timestamp + 121, // 91: gotocompany.siren.v1beta1.NotificationMessage.updated_at:type_name -> google.protobuf.Timestamp + 120, // 92: gotocompany.siren.v1beta1.NotificationMessage.configs:type_name -> google.protobuf.Struct + 88, // 93: gotocompany.siren.v1beta1.ListNotificationMessagesResponse.messages:type_name -> gotocompany.siren.v1beta1.NotificationMessage + 116, // 94: gotocompany.siren.v1beta1.ListNotificationsRequest.labels:type_name -> gotocompany.siren.v1beta1.ListNotificationsRequest.LabelsEntry + 117, // 95: gotocompany.siren.v1beta1.ListNotificationsRequest.receiver_selectors:type_name -> gotocompany.siren.v1beta1.ListNotificationsRequest.ReceiverSelectorsEntry + 118, // 96: gotocompany.siren.v1beta1.ReceiverSelector.receiver_selector:type_name -> gotocompany.siren.v1beta1.ReceiverSelector.ReceiverSelectorEntry + 120, // 97: gotocompany.siren.v1beta1.Notification.data:type_name -> google.protobuf.Struct + 119, // 98: gotocompany.siren.v1beta1.Notification.labels:type_name -> gotocompany.siren.v1beta1.Notification.LabelsEntry + 122, // 99: gotocompany.siren.v1beta1.Notification.valid_duration:type_name -> google.protobuf.Duration + 121, // 100: gotocompany.siren.v1beta1.Notification.create_at:type_name -> google.protobuf.Timestamp + 91, // 101: gotocompany.siren.v1beta1.Notification.receiver_selectors:type_name -> gotocompany.siren.v1beta1.ReceiverSelector + 92, // 102: gotocompany.siren.v1beta1.ListNotificationsResponse.notifications:type_name -> gotocompany.siren.v1beta1.Notification + 1, // 103: gotocompany.siren.v1beta1.SirenService.ListProviders:input_type -> gotocompany.siren.v1beta1.ListProvidersRequest + 3, // 104: gotocompany.siren.v1beta1.SirenService.CreateProvider:input_type -> gotocompany.siren.v1beta1.CreateProviderRequest + 5, // 105: gotocompany.siren.v1beta1.SirenService.GetProvider:input_type -> gotocompany.siren.v1beta1.GetProviderRequest + 7, // 106: gotocompany.siren.v1beta1.SirenService.UpdateProvider:input_type -> gotocompany.siren.v1beta1.UpdateProviderRequest + 9, // 107: gotocompany.siren.v1beta1.SirenService.DeleteProvider:input_type -> gotocompany.siren.v1beta1.DeleteProviderRequest + 45, // 108: gotocompany.siren.v1beta1.SirenService.NotifyReceiver:input_type -> gotocompany.siren.v1beta1.NotifyReceiverRequest + 12, // 109: gotocompany.siren.v1beta1.SirenService.ListNamespaces:input_type -> gotocompany.siren.v1beta1.ListNamespacesRequest + 14, // 110: gotocompany.siren.v1beta1.SirenService.CreateNamespace:input_type -> gotocompany.siren.v1beta1.CreateNamespaceRequest + 16, // 111: gotocompany.siren.v1beta1.SirenService.GetNamespace:input_type -> gotocompany.siren.v1beta1.GetNamespaceRequest + 18, // 112: gotocompany.siren.v1beta1.SirenService.UpdateNamespace:input_type -> gotocompany.siren.v1beta1.UpdateNamespaceRequest + 20, // 113: gotocompany.siren.v1beta1.SirenService.DeleteNamespace:input_type -> gotocompany.siren.v1beta1.DeleteNamespaceRequest + 24, // 114: gotocompany.siren.v1beta1.SirenService.ListSubscriptions:input_type -> gotocompany.siren.v1beta1.ListSubscriptionsRequest + 26, // 115: gotocompany.siren.v1beta1.SirenService.CreateSubscription:input_type -> gotocompany.siren.v1beta1.CreateSubscriptionRequest + 28, // 116: gotocompany.siren.v1beta1.SirenService.GetSubscription:input_type -> gotocompany.siren.v1beta1.GetSubscriptionRequest + 30, // 117: gotocompany.siren.v1beta1.SirenService.UpdateSubscription:input_type -> gotocompany.siren.v1beta1.UpdateSubscriptionRequest + 32, // 118: gotocompany.siren.v1beta1.SirenService.DeleteSubscription:input_type -> gotocompany.siren.v1beta1.DeleteSubscriptionRequest + 35, // 119: gotocompany.siren.v1beta1.SirenService.ListReceivers:input_type -> gotocompany.siren.v1beta1.ListReceiversRequest + 37, // 120: gotocompany.siren.v1beta1.SirenService.CreateReceiver:input_type -> gotocompany.siren.v1beta1.CreateReceiverRequest + 39, // 121: gotocompany.siren.v1beta1.SirenService.GetReceiver:input_type -> gotocompany.siren.v1beta1.GetReceiverRequest + 41, // 122: gotocompany.siren.v1beta1.SirenService.UpdateReceiver:input_type -> gotocompany.siren.v1beta1.UpdateReceiverRequest + 43, // 123: gotocompany.siren.v1beta1.SirenService.DeleteReceiver:input_type -> gotocompany.siren.v1beta1.DeleteReceiverRequest + 48, // 124: gotocompany.siren.v1beta1.SirenService.ListAlerts:input_type -> gotocompany.siren.v1beta1.ListAlertsRequest + 50, // 125: gotocompany.siren.v1beta1.SirenService.CreateAlerts:input_type -> gotocompany.siren.v1beta1.CreateAlertsRequest + 52, // 126: gotocompany.siren.v1beta1.SirenService.CreateAlertsWithNamespace:input_type -> gotocompany.siren.v1beta1.CreateAlertsWithNamespaceRequest + 58, // 127: gotocompany.siren.v1beta1.SirenService.ListRules:input_type -> gotocompany.siren.v1beta1.ListRulesRequest + 60, // 128: gotocompany.siren.v1beta1.SirenService.UpdateRule:input_type -> gotocompany.siren.v1beta1.UpdateRuleRequest + 64, // 129: gotocompany.siren.v1beta1.SirenService.ListTemplates:input_type -> gotocompany.siren.v1beta1.ListTemplatesRequest + 68, // 130: gotocompany.siren.v1beta1.SirenService.GetTemplate:input_type -> gotocompany.siren.v1beta1.GetTemplateRequest + 66, // 131: gotocompany.siren.v1beta1.SirenService.UpsertTemplate:input_type -> gotocompany.siren.v1beta1.UpsertTemplateRequest + 70, // 132: gotocompany.siren.v1beta1.SirenService.DeleteTemplate:input_type -> gotocompany.siren.v1beta1.DeleteTemplateRequest + 72, // 133: gotocompany.siren.v1beta1.SirenService.RenderTemplate:input_type -> gotocompany.siren.v1beta1.RenderTemplateRequest + 75, // 134: gotocompany.siren.v1beta1.SirenService.CreateSilence:input_type -> gotocompany.siren.v1beta1.CreateSilenceRequest + 77, // 135: gotocompany.siren.v1beta1.SirenService.ListSilences:input_type -> gotocompany.siren.v1beta1.ListSilencesRequest + 79, // 136: gotocompany.siren.v1beta1.SirenService.GetSilence:input_type -> gotocompany.siren.v1beta1.GetSilenceRequest + 81, // 137: gotocompany.siren.v1beta1.SirenService.ExpireSilence:input_type -> gotocompany.siren.v1beta1.ExpireSilenceRequest + 83, // 138: gotocompany.siren.v1beta1.SirenService.PostNotification:input_type -> gotocompany.siren.v1beta1.PostNotificationRequest + 85, // 139: gotocompany.siren.v1beta1.SirenService.PostBulkNotifications:input_type -> gotocompany.siren.v1beta1.PostBulkNotificationsRequest + 87, // 140: gotocompany.siren.v1beta1.SirenService.ListNotificationMessages:input_type -> gotocompany.siren.v1beta1.ListNotificationMessagesRequest + 90, // 141: gotocompany.siren.v1beta1.SirenService.ListNotifications:input_type -> gotocompany.siren.v1beta1.ListNotificationsRequest + 2, // 142: gotocompany.siren.v1beta1.SirenService.ListProviders:output_type -> gotocompany.siren.v1beta1.ListProvidersResponse + 4, // 143: gotocompany.siren.v1beta1.SirenService.CreateProvider:output_type -> gotocompany.siren.v1beta1.CreateProviderResponse + 6, // 144: gotocompany.siren.v1beta1.SirenService.GetProvider:output_type -> gotocompany.siren.v1beta1.GetProviderResponse + 8, // 145: gotocompany.siren.v1beta1.SirenService.UpdateProvider:output_type -> gotocompany.siren.v1beta1.UpdateProviderResponse + 10, // 146: gotocompany.siren.v1beta1.SirenService.DeleteProvider:output_type -> gotocompany.siren.v1beta1.DeleteProviderResponse + 46, // 147: gotocompany.siren.v1beta1.SirenService.NotifyReceiver:output_type -> gotocompany.siren.v1beta1.NotifyReceiverResponse + 13, // 148: gotocompany.siren.v1beta1.SirenService.ListNamespaces:output_type -> gotocompany.siren.v1beta1.ListNamespacesResponse + 15, // 149: gotocompany.siren.v1beta1.SirenService.CreateNamespace:output_type -> gotocompany.siren.v1beta1.CreateNamespaceResponse + 17, // 150: gotocompany.siren.v1beta1.SirenService.GetNamespace:output_type -> gotocompany.siren.v1beta1.GetNamespaceResponse + 19, // 151: gotocompany.siren.v1beta1.SirenService.UpdateNamespace:output_type -> gotocompany.siren.v1beta1.UpdateNamespaceResponse + 21, // 152: gotocompany.siren.v1beta1.SirenService.DeleteNamespace:output_type -> gotocompany.siren.v1beta1.DeleteNamespaceResponse + 25, // 153: gotocompany.siren.v1beta1.SirenService.ListSubscriptions:output_type -> gotocompany.siren.v1beta1.ListSubscriptionsResponse + 27, // 154: gotocompany.siren.v1beta1.SirenService.CreateSubscription:output_type -> gotocompany.siren.v1beta1.CreateSubscriptionResponse + 29, // 155: gotocompany.siren.v1beta1.SirenService.GetSubscription:output_type -> gotocompany.siren.v1beta1.GetSubscriptionResponse + 31, // 156: gotocompany.siren.v1beta1.SirenService.UpdateSubscription:output_type -> gotocompany.siren.v1beta1.UpdateSubscriptionResponse + 33, // 157: gotocompany.siren.v1beta1.SirenService.DeleteSubscription:output_type -> gotocompany.siren.v1beta1.DeleteSubscriptionResponse + 36, // 158: gotocompany.siren.v1beta1.SirenService.ListReceivers:output_type -> gotocompany.siren.v1beta1.ListReceiversResponse + 38, // 159: gotocompany.siren.v1beta1.SirenService.CreateReceiver:output_type -> gotocompany.siren.v1beta1.CreateReceiverResponse + 40, // 160: gotocompany.siren.v1beta1.SirenService.GetReceiver:output_type -> gotocompany.siren.v1beta1.GetReceiverResponse + 42, // 161: gotocompany.siren.v1beta1.SirenService.UpdateReceiver:output_type -> gotocompany.siren.v1beta1.UpdateReceiverResponse + 44, // 162: gotocompany.siren.v1beta1.SirenService.DeleteReceiver:output_type -> gotocompany.siren.v1beta1.DeleteReceiverResponse + 49, // 163: gotocompany.siren.v1beta1.SirenService.ListAlerts:output_type -> gotocompany.siren.v1beta1.ListAlertsResponse + 51, // 164: gotocompany.siren.v1beta1.SirenService.CreateAlerts:output_type -> gotocompany.siren.v1beta1.CreateAlertsResponse + 53, // 165: gotocompany.siren.v1beta1.SirenService.CreateAlertsWithNamespace:output_type -> gotocompany.siren.v1beta1.CreateAlertsWithNamespaceResponse + 59, // 166: gotocompany.siren.v1beta1.SirenService.ListRules:output_type -> gotocompany.siren.v1beta1.ListRulesResponse + 61, // 167: gotocompany.siren.v1beta1.SirenService.UpdateRule:output_type -> gotocompany.siren.v1beta1.UpdateRuleResponse + 65, // 168: gotocompany.siren.v1beta1.SirenService.ListTemplates:output_type -> gotocompany.siren.v1beta1.ListTemplatesResponse + 69, // 169: gotocompany.siren.v1beta1.SirenService.GetTemplate:output_type -> gotocompany.siren.v1beta1.GetTemplateResponse + 67, // 170: gotocompany.siren.v1beta1.SirenService.UpsertTemplate:output_type -> gotocompany.siren.v1beta1.UpsertTemplateResponse + 71, // 171: gotocompany.siren.v1beta1.SirenService.DeleteTemplate:output_type -> gotocompany.siren.v1beta1.DeleteTemplateResponse + 73, // 172: gotocompany.siren.v1beta1.SirenService.RenderTemplate:output_type -> gotocompany.siren.v1beta1.RenderTemplateResponse + 76, // 173: gotocompany.siren.v1beta1.SirenService.CreateSilence:output_type -> gotocompany.siren.v1beta1.CreateSilenceResponse + 78, // 174: gotocompany.siren.v1beta1.SirenService.ListSilences:output_type -> gotocompany.siren.v1beta1.ListSilencesResponse + 80, // 175: gotocompany.siren.v1beta1.SirenService.GetSilence:output_type -> gotocompany.siren.v1beta1.GetSilenceResponse + 82, // 176: gotocompany.siren.v1beta1.SirenService.ExpireSilence:output_type -> gotocompany.siren.v1beta1.ExpireSilenceResponse + 84, // 177: gotocompany.siren.v1beta1.SirenService.PostNotification:output_type -> gotocompany.siren.v1beta1.PostNotificationResponse + 86, // 178: gotocompany.siren.v1beta1.SirenService.PostBulkNotifications:output_type -> gotocompany.siren.v1beta1.PostBulkNotificationsResponse + 89, // 179: gotocompany.siren.v1beta1.SirenService.ListNotificationMessages:output_type -> gotocompany.siren.v1beta1.ListNotificationMessagesResponse + 93, // 180: gotocompany.siren.v1beta1.SirenService.ListNotifications:output_type -> gotocompany.siren.v1beta1.ListNotificationsResponse + 142, // [142:181] is the sub-list for method output_type + 103, // [103:142] is the sub-list for method input_type + 103, // [103:103] is the sub-list for extension type_name + 103, // [103:103] is the sub-list for extension extendee + 0, // [0:103] is the sub-list for field type_name } func init() { file_gotocompany_siren_v1beta1_siren_proto_init() } @@ -8618,7 +8743,7 @@ func file_gotocompany_siren_v1beta1_siren_proto_init() { } } file_gotocompany_siren_v1beta1_siren_proto_msgTypes[85].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListNotificationMessagesRequest); i { + switch v := v.(*PostBulkNotificationsRequest); i { case 0: return &v.state case 1: @@ -8630,7 +8755,7 @@ func file_gotocompany_siren_v1beta1_siren_proto_init() { } } file_gotocompany_siren_v1beta1_siren_proto_msgTypes[86].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NotificationMessage); i { + switch v := v.(*PostBulkNotificationsResponse); i { case 0: return &v.state case 1: @@ -8642,7 +8767,7 @@ func file_gotocompany_siren_v1beta1_siren_proto_init() { } } file_gotocompany_siren_v1beta1_siren_proto_msgTypes[87].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListNotificationMessagesResponse); i { + switch v := v.(*ListNotificationMessagesRequest); i { case 0: return &v.state case 1: @@ -8654,7 +8779,7 @@ func file_gotocompany_siren_v1beta1_siren_proto_init() { } } file_gotocompany_siren_v1beta1_siren_proto_msgTypes[88].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListNotificationsRequest); i { + switch v := v.(*NotificationMessage); i { case 0: return &v.state case 1: @@ -8666,7 +8791,7 @@ func file_gotocompany_siren_v1beta1_siren_proto_init() { } } file_gotocompany_siren_v1beta1_siren_proto_msgTypes[89].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ReceiverSelector); i { + switch v := v.(*ListNotificationMessagesResponse); i { case 0: return &v.state case 1: @@ -8678,7 +8803,7 @@ func file_gotocompany_siren_v1beta1_siren_proto_init() { } } file_gotocompany_siren_v1beta1_siren_proto_msgTypes[90].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Notification); i { + switch v := v.(*ListNotificationsRequest); i { case 0: return &v.state case 1: @@ -8690,6 +8815,30 @@ func file_gotocompany_siren_v1beta1_siren_proto_init() { } } file_gotocompany_siren_v1beta1_siren_proto_msgTypes[91].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReceiverSelector); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_gotocompany_siren_v1beta1_siren_proto_msgTypes[92].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Notification); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_gotocompany_siren_v1beta1_siren_proto_msgTypes[93].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ListNotificationsResponse); i { case 0: return &v.state @@ -8708,7 +8857,7 @@ func file_gotocompany_siren_v1beta1_siren_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_gotocompany_siren_v1beta1_siren_proto_rawDesc, NumEnums: 0, - NumMessages: 118, + NumMessages: 120, NumExtensions: 0, NumServices: 1, }, diff --git a/proto/gotocompany/siren/v1beta1/siren.pb.gw.go b/proto/gotocompany/siren/v1beta1/siren.pb.gw.go index b4b66c25..73e9e5f1 100644 --- a/proto/gotocompany/siren/v1beta1/siren.pb.gw.go +++ b/proto/gotocompany/siren/v1beta1/siren.pb.gw.go @@ -1855,6 +1855,40 @@ func local_request_SirenService_PostNotification_0(ctx context.Context, marshale } +func request_SirenService_PostBulkNotifications_0(ctx context.Context, marshaler runtime.Marshaler, client SirenServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq PostBulkNotificationsRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.PostBulkNotifications(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_SirenService_PostBulkNotifications_0(ctx context.Context, marshaler runtime.Marshaler, server SirenServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq PostBulkNotificationsRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.PostBulkNotifications(ctx, &protoReq) + return msg, metadata, err + +} + func request_SirenService_ListNotificationMessages_0(ctx context.Context, marshaler runtime.Marshaler, client SirenServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq ListNotificationMessagesRequest var metadata runtime.ServerMetadata @@ -2849,6 +2883,31 @@ func RegisterSirenServiceHandlerServer(ctx context.Context, mux *runtime.ServeMu }) + mux.Handle("POST", pattern_SirenService_PostBulkNotifications_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gotocompany.siren.v1beta1.SirenService/PostBulkNotifications", runtime.WithHTTPPathPattern("/v1beta1/bulk-notifications")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_SirenService_PostBulkNotifications_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SirenService_PostBulkNotifications_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_SirenService_ListNotificationMessages_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -3732,6 +3791,28 @@ func RegisterSirenServiceHandlerClient(ctx context.Context, mux *runtime.ServeMu }) + mux.Handle("POST", pattern_SirenService_PostBulkNotifications_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gotocompany.siren.v1beta1.SirenService/PostBulkNotifications", runtime.WithHTTPPathPattern("/v1beta1/bulk-notifications")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_SirenService_PostBulkNotifications_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SirenService_PostBulkNotifications_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_SirenService_ListNotificationMessages_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -3852,6 +3933,8 @@ var ( pattern_SirenService_PostNotification_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1beta1", "notifications"}, "")) + pattern_SirenService_PostBulkNotifications_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1beta1", "bulk-notifications"}, "")) + pattern_SirenService_ListNotificationMessages_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"v1beta1", "notifications", "notification_id", "messages"}, "")) pattern_SirenService_ListNotifications_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1beta1", "notifications"}, "")) @@ -3930,6 +4013,8 @@ var ( forward_SirenService_PostNotification_0 = runtime.ForwardResponseMessage + forward_SirenService_PostBulkNotifications_0 = runtime.ForwardResponseMessage + forward_SirenService_ListNotificationMessages_0 = runtime.ForwardResponseMessage forward_SirenService_ListNotifications_0 = runtime.ForwardResponseMessage diff --git a/proto/gotocompany/siren/v1beta1/siren.pb.validate.go b/proto/gotocompany/siren/v1beta1/siren.pb.validate.go index c2918bca..1b504d9b 100644 --- a/proto/gotocompany/siren/v1beta1/siren.pb.validate.go +++ b/proto/gotocompany/siren/v1beta1/siren.pb.validate.go @@ -11252,6 +11252,246 @@ var _ interface { ErrorName() string } = PostNotificationResponseValidationError{} +// Validate checks the field values on PostBulkNotificationsRequest with the +// rules defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *PostBulkNotificationsRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on PostBulkNotificationsRequest with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// PostBulkNotificationsRequestMultiError, or nil if none found. +func (m *PostBulkNotificationsRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *PostBulkNotificationsRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + for idx, item := range m.GetNotifications() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, PostBulkNotificationsRequestValidationError{ + field: fmt.Sprintf("Notifications[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, PostBulkNotificationsRequestValidationError{ + field: fmt.Sprintf("Notifications[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return PostBulkNotificationsRequestValidationError{ + field: fmt.Sprintf("Notifications[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + + if len(errors) > 0 { + return PostBulkNotificationsRequestMultiError(errors) + } + + return nil +} + +// PostBulkNotificationsRequestMultiError is an error wrapping multiple +// validation errors returned by PostBulkNotificationsRequest.ValidateAll() if +// the designated constraints aren't met. +type PostBulkNotificationsRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m PostBulkNotificationsRequestMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m PostBulkNotificationsRequestMultiError) AllErrors() []error { return m } + +// PostBulkNotificationsRequestValidationError is the validation error returned +// by PostBulkNotificationsRequest.Validate if the designated constraints +// aren't met. +type PostBulkNotificationsRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e PostBulkNotificationsRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e PostBulkNotificationsRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e PostBulkNotificationsRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e PostBulkNotificationsRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e PostBulkNotificationsRequestValidationError) ErrorName() string { + return "PostBulkNotificationsRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e PostBulkNotificationsRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sPostBulkNotificationsRequest.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = PostBulkNotificationsRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = PostBulkNotificationsRequestValidationError{} + +// Validate checks the field values on PostBulkNotificationsResponse with the +// rules defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *PostBulkNotificationsResponse) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on PostBulkNotificationsResponse with +// the rules defined in the proto definition for this message. If any rules +// are violated, the result is a list of violation errors wrapped in +// PostBulkNotificationsResponseMultiError, or nil if none found. +func (m *PostBulkNotificationsResponse) ValidateAll() error { + return m.validate(true) +} + +func (m *PostBulkNotificationsResponse) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if len(errors) > 0 { + return PostBulkNotificationsResponseMultiError(errors) + } + + return nil +} + +// PostBulkNotificationsResponseMultiError is an error wrapping multiple +// validation errors returned by PostBulkNotificationsResponse.ValidateAll() +// if the designated constraints aren't met. +type PostBulkNotificationsResponseMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m PostBulkNotificationsResponseMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m PostBulkNotificationsResponseMultiError) AllErrors() []error { return m } + +// PostBulkNotificationsResponseValidationError is the validation error +// returned by PostBulkNotificationsResponse.Validate if the designated +// constraints aren't met. +type PostBulkNotificationsResponseValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e PostBulkNotificationsResponseValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e PostBulkNotificationsResponseValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e PostBulkNotificationsResponseValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e PostBulkNotificationsResponseValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e PostBulkNotificationsResponseValidationError) ErrorName() string { + return "PostBulkNotificationsResponseValidationError" +} + +// Error satisfies the builtin error interface +func (e PostBulkNotificationsResponseValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sPostBulkNotificationsResponse.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = PostBulkNotificationsResponseValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = PostBulkNotificationsResponseValidationError{} + // Validate checks the field values on ListNotificationMessagesRequest with the // rules defined in the proto definition for this message. If any rules are // violated, the first error encountered is returned, or nil if there are no violations. @@ -11381,8 +11621,6 @@ func (m *NotificationMessage) validate(all bool) error { // no validation rules for Id - // no validation rules for NotificationId - // no validation rules for Status // no validation rules for ReceiverType diff --git a/proto/gotocompany/siren/v1beta1/siren_grpc.pb.go b/proto/gotocompany/siren/v1beta1/siren_grpc.pb.go index 3c15b783..d005e7a1 100644 --- a/proto/gotocompany/siren/v1beta1/siren_grpc.pb.go +++ b/proto/gotocompany/siren/v1beta1/siren_grpc.pb.go @@ -59,6 +59,7 @@ type SirenServiceClient interface { GetSilence(ctx context.Context, in *GetSilenceRequest, opts ...grpc.CallOption) (*GetSilenceResponse, error) ExpireSilence(ctx context.Context, in *ExpireSilenceRequest, opts ...grpc.CallOption) (*ExpireSilenceResponse, error) PostNotification(ctx context.Context, in *PostNotificationRequest, opts ...grpc.CallOption) (*PostNotificationResponse, error) + PostBulkNotifications(ctx context.Context, in *PostBulkNotificationsRequest, opts ...grpc.CallOption) (*PostBulkNotificationsResponse, error) ListNotificationMessages(ctx context.Context, in *ListNotificationMessagesRequest, opts ...grpc.CallOption) (*ListNotificationMessagesResponse, error) ListNotifications(ctx context.Context, in *ListNotificationsRequest, opts ...grpc.CallOption) (*ListNotificationsResponse, error) } @@ -396,6 +397,15 @@ func (c *sirenServiceClient) PostNotification(ctx context.Context, in *PostNotif return out, nil } +func (c *sirenServiceClient) PostBulkNotifications(ctx context.Context, in *PostBulkNotificationsRequest, opts ...grpc.CallOption) (*PostBulkNotificationsResponse, error) { + out := new(PostBulkNotificationsResponse) + err := c.cc.Invoke(ctx, "/gotocompany.siren.v1beta1.SirenService/PostBulkNotifications", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *sirenServiceClient) ListNotificationMessages(ctx context.Context, in *ListNotificationMessagesRequest, opts ...grpc.CallOption) (*ListNotificationMessagesResponse, error) { out := new(ListNotificationMessagesResponse) err := c.cc.Invoke(ctx, "/gotocompany.siren.v1beta1.SirenService/ListNotificationMessages", in, out, opts...) @@ -455,6 +465,7 @@ type SirenServiceServer interface { GetSilence(context.Context, *GetSilenceRequest) (*GetSilenceResponse, error) ExpireSilence(context.Context, *ExpireSilenceRequest) (*ExpireSilenceResponse, error) PostNotification(context.Context, *PostNotificationRequest) (*PostNotificationResponse, error) + PostBulkNotifications(context.Context, *PostBulkNotificationsRequest) (*PostBulkNotificationsResponse, error) ListNotificationMessages(context.Context, *ListNotificationMessagesRequest) (*ListNotificationMessagesResponse, error) ListNotifications(context.Context, *ListNotificationsRequest) (*ListNotificationsResponse, error) mustEmbedUnimplementedSirenServiceServer() @@ -572,6 +583,9 @@ func (UnimplementedSirenServiceServer) ExpireSilence(context.Context, *ExpireSil func (UnimplementedSirenServiceServer) PostNotification(context.Context, *PostNotificationRequest) (*PostNotificationResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method PostNotification not implemented") } +func (UnimplementedSirenServiceServer) PostBulkNotifications(context.Context, *PostBulkNotificationsRequest) (*PostBulkNotificationsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PostBulkNotifications not implemented") +} func (UnimplementedSirenServiceServer) ListNotificationMessages(context.Context, *ListNotificationMessagesRequest) (*ListNotificationMessagesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListNotificationMessages not implemented") } @@ -1239,6 +1253,24 @@ func _SirenService_PostNotification_Handler(srv interface{}, ctx context.Context return interceptor(ctx, in, info, handler) } +func _SirenService_PostBulkNotifications_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PostBulkNotificationsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SirenServiceServer).PostBulkNotifications(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gotocompany.siren.v1beta1.SirenService/PostBulkNotifications", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SirenServiceServer).PostBulkNotifications(ctx, req.(*PostBulkNotificationsRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _SirenService_ListNotificationMessages_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListNotificationMessagesRequest) if err := dec(in); err != nil { @@ -1426,6 +1458,10 @@ var SirenService_ServiceDesc = grpc.ServiceDesc{ MethodName: "PostNotification", Handler: _SirenService_PostNotification_Handler, }, + { + MethodName: "PostBulkNotifications", + Handler: _SirenService_PostBulkNotifications_Handler, + }, { MethodName: "ListNotificationMessages", Handler: _SirenService_ListNotificationMessages_Handler, diff --git a/proto/siren.swagger.yaml b/proto/siren.swagger.yaml index 628c9689..d9a4374d 100644 --- a/proto/siren.swagger.yaml +++ b/proto/siren.swagger.yaml @@ -127,6 +127,27 @@ paths: type: object tags: - Alert + /v1beta1/bulk-notifications: + post: + summary: Post bulk event notifications + operationId: SirenService_PostBulkNotifications + responses: + "200": + description: A successful response. + schema: + $ref: '#/definitions/PostBulkNotificationsResponse' + default: + description: An unexpected error response. + schema: + $ref: '#/definitions/Status' + parameters: + - name: body + in: body + required: true + schema: + $ref: '#/definitions/PostBulkNotificationsRequest' + tags: + - Notification /v1beta1/namespaces: get: summary: list namespaces @@ -1322,8 +1343,10 @@ definitions: properties: id: type: string - notification_id: - type: string + notification_ids: + type: array + items: + type: string status: type: string receiver_type: @@ -1365,6 +1388,21 @@ definitions: The JSON representation for `NullValue` is JSON `null`. - NULL_VALUE: Null value. + PostBulkNotificationsRequest: + type: object + properties: + notifications: + type: array + items: + type: object + $ref: '#/definitions/Notification' + PostBulkNotificationsResponse: + type: object + properties: + notification_ids: + type: array + items: + type: string PostNotificationRequest: type: object properties: diff --git a/test/e2e_test/notification_bulk_subscription_test.go b/test/e2e_test/notification_bulk_subscription_test.go new file mode 100644 index 00000000..561a0434 --- /dev/null +++ b/test/e2e_test/notification_bulk_subscription_test.go @@ -0,0 +1,289 @@ +package e2e_test + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/goto/salt/db" + "github.com/goto/siren/config" + "github.com/goto/siren/core/notification" + "github.com/goto/siren/core/template" + "github.com/goto/siren/internal/server" + "github.com/goto/siren/plugins" + cortexv1plugin "github.com/goto/siren/plugins/providers/cortex/v1" + sirenv1beta1 "github.com/goto/siren/proto/gotocompany/siren/v1beta1" + testdatatemplate_test "github.com/goto/siren/test/e2e_test/testdata/templates" + "github.com/mcuadros/go-defaults" + "github.com/stretchr/testify/suite" + "google.golang.org/protobuf/types/known/structpb" + "gopkg.in/yaml.v3" +) + +type BulkNotificationSubscriptionTestSuite struct { + suite.Suite + cancelContext context.CancelFunc + grpcClient sirenv1beta1.SirenServiceClient + dbClient *db.Client + cancelClient func() + appConfig *config.Config + testBench *CortexTest +} + +func (s *BulkNotificationSubscriptionTestSuite) SetupTest() { + apiHTTPPort, err := getFreePort() + s.Require().Nil(err) + apiGRPCPort, err := getFreePort() + s.Require().Nil(err) + + s.appConfig = &config.Config{} + + defaults.SetDefaults(s.appConfig) + + s.appConfig.Log.Level = "error" + s.appConfig.Service = server.Config{ + Port: apiHTTPPort, + GRPC: server.GRPCConfig{ + Port: apiGRPCPort, + }, + EncryptionKey: testEncryptionKey, + SubscriptionV2Enabled: true, + } + s.appConfig.Notification = notification.Config{ + MessageHandler: notification.HandlerConfig{ + Enabled: false, + }, + DLQHandler: notification.HandlerConfig{ + Enabled: false, + }, + GroupBy: []string{ + "team", + "service", + }, + SubscriptionV2Enabled: true, + } + s.appConfig.Telemetry.OpenTelemetry.Enabled = false + + s.testBench, err = InitCortexEnvironment(s.appConfig) + s.Require().NoError(err) + + // setup custom cortex config + // TODO host.docker.internal only works for docker-desktop to call a service in host (siren) + pathProject, _ := os.Getwd() + rootProject := filepath.Dir(filepath.Dir(pathProject)) + s.appConfig.Providers.PluginPath = filepath.Join(rootProject, "plugin") + s.appConfig.Providers.Plugins = make(map[string]plugins.PluginConfig, 0) + s.appConfig.Providers.Plugins["cortex"] = plugins.PluginConfig{ + Handshake: plugins.HandshakeConfig{ + ProtocolVersion: cortexv1plugin.Handshake.ProtocolVersion, + MagicCookieKey: cortexv1plugin.Handshake.MagicCookieKey, + MagicCookieValue: cortexv1plugin.Handshake.MagicCookieValue, + }, + ServiceConfig: map[string]interface{}{ + "webhook_base_api": fmt.Sprintf("http://test:%d/v1beta1/alerts/cortex", apiHTTPPort), + "group_wait": "1s", + "group_interval": "1s", + "repeat_interval": "1s", + }, + } + + // enable worker + s.appConfig.Notification.MessageHandler.Enabled = true + s.appConfig.Notification.DLQHandler.Enabled = true + + ctx, cancel := context.WithCancel(context.Background()) + s.cancelContext = cancel + + StartSirenServer(ctx, *s.appConfig) + + s.grpcClient, s.cancelClient, err = CreateClient(ctx, fmt.Sprintf("localhost:%d", apiGRPCPort)) + s.Require().NoError(err) + + _, err = s.grpcClient.CreateProvider(ctx, &sirenv1beta1.CreateProviderRequest{ + Host: fmt.Sprintf("http://%s", s.testBench.NginxHost), + Urn: "cortex-test", + Name: "cortex-test", + Type: "cortex", + }) + s.Require().NoError(err) + + s.dbClient, err = db.New(s.testBench.PGConfig) + if err != nil { + s.T().Fatal(err) + } +} + +func (s *BulkNotificationSubscriptionTestSuite) TearDownTest() { + s.cancelClient() + + // Clean tests + err := s.testBench.CleanUp() + s.Require().NoError(err) + + s.cancelContext() +} + +func (s *BulkNotificationSubscriptionTestSuite) TestSendBulkNotification() { + ctx := context.Background() + + _, err := s.grpcClient.CreateNamespace(ctx, &sirenv1beta1.CreateNamespaceRequest{ + Name: "new-gotocompany-1", + Urn: "new-gotocompany-1", + Provider: 1, + Credentials: nil, + Labels: map[string]string{ + "key1": "value1", + }, + }) + s.Require().NoError(err) + + s.Run("sending bulk notification with same group labels should trigger only 1 notification", func() { + waitChan := make(chan struct{}, 1) + + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + bodyBytes, err := io.ReadAll(r.Body) + s.Assert().NoError(err) + fmt.Println(string(bodyBytes)) + type sampleStruct struct { + Title string `json:"title"` + Desription string `json:"description"` + MergedTeam string `json:"merged_team"` + MergedService string `json:"merged_service"` + MergedEnvironment string `json:"merged_environment"` + MergedCategory string `json:"merged_category"` + } + + expectedNotification := sampleStruct{ + Title: "This is the test notification with template", + Desription: `Plain flow scalars are picky about the (:) and (#) characters. +They can be in the string, but (:) cannot appear before a space or newline. +And (#) cannot appear after a space or newline; doing this will cause a syntax error. +If you need to use these characters you are probably better off using one of the quoted styles instead. +`, + MergedCategory: "httpreceiver", + MergedEnvironment: "integration development production", + MergedService: "some-service some-service some-service", + MergedTeam: "gotocompany gotocompany gotocompany", + } + var ( + resultStruct sampleStruct + ) + s.Assert().NoError(json.Unmarshal(bodyBytes, &resultStruct)) + + if diff := cmp.Diff(expectedNotification, resultStruct); diff != "" { + s.T().Errorf("got diff: %v", diff) + } + waitChan <- struct{}{} + + })) + defer testServer.Close() + + configs, err := structpb.NewStruct(map[string]any{ + "url": testServer.URL, + }) + s.Require().NoError(err) + _, err = s.grpcClient.CreateReceiver(ctx, &sirenv1beta1.CreateReceiverRequest{ + Name: "gotocompany-http", + Type: "http", + Labels: map[string]string{ + "entity": "gotocompany", + "kind": "http", + "id": "1", + }, + Configurations: configs, + }) + s.Require().NoError(err) + + sampleTemplateFile, err := template.YamlStringToFile(testdatatemplate_test.SampleBulkMessageTemplate) + s.Require().NoError(err) + + body, err := yaml.Marshal(sampleTemplateFile.Body) + s.Require().NoError(err) + + variables := make([]*sirenv1beta1.TemplateVariables, 0) + for _, variable := range sampleTemplateFile.Variables { + variables = append(variables, &sirenv1beta1.TemplateVariables{ + Name: variable.Name, + Type: variable.Type, + Default: variable.Default, + Description: variable.Description, + }) + } + + _, err = s.grpcClient.UpsertTemplate(ctx, &sirenv1beta1.UpsertTemplateRequest{ + Name: sampleTemplateFile.Name, + Body: string(body), + Tags: sampleTemplateFile.Tags, + Variables: variables, + }) + s.Require().NoError(err) + + _, err = s.grpcClient.CreateSubscription(ctx, &sirenv1beta1.CreateSubscriptionRequest{ + Urn: "subscribe-http-three", + Namespace: 1, + Receivers: []*sirenv1beta1.ReceiverMetadata{ + { + Id: 1, + }, + }, + Match: map[string]string{ + "team": "gotocompany", + "service": "some-service", + }, + }) + s.Require().NoError(err) + + data, err := structpb.NewStruct(map[string]any{ + "title": "This is the test notification with template", + "icon_emoji": ":smile:", + }) + s.Require().NoError(err) + + _, err = s.grpcClient.PostBulkNotifications(ctx, &sirenv1beta1.PostBulkNotificationsRequest{ + Notifications: []*sirenv1beta1.Notification{ + { + Data: data, + Labels: map[string]string{ + "team": "gotocompany", + "service": "some-service", + "environment": "integration", + "category": "httpreceiver", + }, + Template: sampleTemplateFile.Name, + }, + { + Data: data, + Labels: map[string]string{ + "team": "gotocompany", + "service": "some-service", + "environment": "development", + }, + Template: sampleTemplateFile.Name, + }, + { + Data: data, + Labels: map[string]string{ + "team": "gotocompany", + "service": "some-service", + "environment": "production", + }, + Template: sampleTemplateFile.Name, + }, + }, + }) + s.Assert().NoError(err) + + <-waitChan + }) +} + +func TestBulkNotificationSubscriptionTestSuite(t *testing.T) { + suite.Run(t, new(BulkNotificationSubscriptionTestSuite)) +} diff --git a/test/e2e_test/notification_receiver_test.go b/test/e2e_test/notification_receiver_test.go index 57a2c416..5696dd1b 100644 --- a/test/e2e_test/notification_receiver_test.go +++ b/test/e2e_test/notification_receiver_test.go @@ -103,7 +103,7 @@ func (s *NotificationReceiverTestSuite) TearDownTest() { s.cancelContext() } -func (s *NotificationReceiverTestSuite) TestSendNotification() { +func (s *NotificationReceiverTestSuite) TestSendNotificationSuccess() { ctx := context.Background() controlChan := make(chan struct{}, 1) @@ -173,6 +173,93 @@ func (s *NotificationReceiverTestSuite) TestSendNotification() { } +func (s *NotificationReceiverTestSuite) TestSendNotificationFailureLimited() { + ctx := context.Background() + + // add test server http receiver + configs, err := structpb.NewStruct(map[string]any{ + "url": "dummy", + }) + s.Require().NoError(err) + rcv, err := s.client.CreateReceiver(ctx, &sirenv1beta1.CreateReceiverRequest{ + Name: "notification-http", + Type: "http", + Labels: nil, + Configurations: configs, + }) + s.Require().NoError(err) + + time.Sleep(100 * time.Millisecond) + + _, err = s.client.PostNotification(ctx, &sirenv1beta1.PostNotificationRequest{ + Receivers: []*structpb.Struct{ + { + Fields: map[string]*structpb.Value{ + "id": structpb.NewStringValue(fmt.Sprintf("%d", rcv.GetId())), + }, + }, + { + Fields: map[string]*structpb.Value{ + "id": structpb.NewStringValue(fmt.Sprintf("%d", rcv.GetId())), + }, + }, + { + Fields: map[string]*structpb.Value{ + "id": structpb.NewStringValue(fmt.Sprintf("%d", rcv.GetId())), + }, + }, + { + Fields: map[string]*structpb.Value{ + "id": structpb.NewStringValue(fmt.Sprintf("%d", rcv.GetId())), + }, + }, + { + Fields: map[string]*structpb.Value{ + "id": structpb.NewStringValue(fmt.Sprintf("%d", rcv.GetId())), + }, + }, + { + Fields: map[string]*structpb.Value{ + "id": structpb.NewStringValue(fmt.Sprintf("%d", rcv.GetId())), + }, + }, + { + Fields: map[string]*structpb.Value{ + "id": structpb.NewStringValue(fmt.Sprintf("%d", rcv.GetId())), + }, + }, + { + Fields: map[string]*structpb.Value{ + "id": structpb.NewStringValue(fmt.Sprintf("%d", rcv.GetId())), + }, + }, + { + Fields: map[string]*structpb.Value{ + "id": structpb.NewStringValue(fmt.Sprintf("%d", rcv.GetId())), + }, + }, + { + Fields: map[string]*structpb.Value{ + "id": structpb.NewStringValue(fmt.Sprintf("%d", rcv.GetId())), + }, + }, + { + Fields: map[string]*structpb.Value{ + "id": structpb.NewStringValue(fmt.Sprintf("%d", rcv.GetId())), + }, + }, + }, + Data: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "text": structpb.NewStringValue("test send notification"), + "icon_emoji": structpb.NewStringValue(":smile:"), + }, + }, + }) + s.Require().Equal("rpc error: code = InvalidArgument desc = number of receiver selectors should be less than or equal threshold 10", err.Error()) + +} + func TestNotificationReceiverTestSuite(t *testing.T) { suite.Run(t, new(NotificationReceiverTestSuite)) } diff --git a/test/e2e_test/notification_subscription_test.go b/test/e2e_test/notification_subscription_test.go index 857c92e2..0bbc3151 100644 --- a/test/e2e_test/notification_subscription_test.go +++ b/test/e2e_test/notification_subscription_test.go @@ -67,7 +67,7 @@ func (s *NotificationSubscriptionTestSuite) SetupTest() { s.testBench, err = InitCortexEnvironment(s.appConfig) s.Require().NoError(err) - // setup custom cortex configĂ„ + // setup custom cortex config // TODO host.docker.internal only works for docker-desktop to call a service in host (siren) pathProject, _ := os.Getwd() rootProject := filepath.Dir(filepath.Dir(pathProject)) diff --git a/test/e2e_test/notification_template_test.go b/test/e2e_test/notification_template_test.go index 35c2db56..9dcaee60 100644 --- a/test/e2e_test/notification_template_test.go +++ b/test/e2e_test/notification_template_test.go @@ -60,6 +60,7 @@ func (s *NotificationTemplateTestSuite) SetupTest() { DLQHandler: notification.HandlerConfig{ Enabled: false, }, + SubscriptionV2Enabled: true, } s.appConfig.Telemetry.OpenTelemetry.Enabled = false diff --git a/test/e2e_test/testdata/templates/template-bulk-message-template.yaml b/test/e2e_test/testdata/templates/template-bulk-message-template.yaml new file mode 100644 index 00000000..e48769fd --- /dev/null +++ b/test/e2e_test/testdata/templates/template-bulk-message-template.yaml @@ -0,0 +1,18 @@ +apiVersion: v2 +type: template +name: test-message +body: + - receiver_type: http + content: |- + title: {{.Data.title}} + description: | + Plain flow scalars are picky about the (:) and (#) characters. + They can be in the string, but (:) cannot appear before a space or newline. + And (#) cannot appear after a space or newline; doing this will cause a syntax error. + If you need to use these characters you are probably better off using one of the quoted styles instead. + merged_team: {{range $val := .MergedLabels.team}}{{$val}} {{end}} + merged_service: {{range $val := .MergedLabels.service}}{{$val}} {{end}} + merged_environment: {{range $val := .MergedLabels.environment}}{{$val}} {{end}} + merged_category: {{range $val := .MergedLabels.category}}{{$val}} {{end}} +tags: + - test-message \ No newline at end of file diff --git a/test/e2e_test/testdata/templates/template.go b/test/e2e_test/testdata/templates/template.go index 167722c4..e3d7b461 100644 --- a/test/e2e_test/testdata/templates/template.go +++ b/test/e2e_test/testdata/templates/template.go @@ -9,4 +9,6 @@ var ( SampleRuleTemplate string //go:embed template-message-sample-1.yaml SampleMessageTemplate string + //go:embed template-bulk-message-template.yaml + SampleBulkMessageTemplate string )