1
0
mirror of https://github.com/bitwarden/server synced 2025-12-11 22:03:38 +00:00

[PM-17562] Refactor event integration methods / declarations in ServiceCollectionExtensions (#6118)

* [PM-17562] Refactor event integration methods / declarations in ServiceCollectionExtensions

* Refactored ServiceCollectionExtensions to use TryAdd and still launch unique listeneer services

* Updated unit tests to match new generic format for Listeners

* Fix method spacing

* Update README to reflect new integration setup in ServiceCollectionExtensions

* Move interfaces to I prefix; fix typo in subscription

* Fix reference to IIntegrationListenerConfiguration
This commit is contained in:
Brant DeBow
2025-07-29 11:22:21 -04:00
committed by GitHub
parent 43372b7168
commit a84e5554fb
19 changed files with 512 additions and 314 deletions

View File

@@ -373,7 +373,6 @@ public static class ServiceCollectionExtensions
services.AddSingleton<IPushRegistrationService, NoopPushRegistrationService>();
services.AddSingleton<IAttachmentStorageService, NoopAttachmentStorageService>();
services.AddSingleton<ILicensingService, NoopLicensingService>();
services.AddSingleton<IEventWriteService, NoopEventWriteService>();
}
public static IdentityBuilder AddCustomIdentityServices(
@@ -550,198 +549,57 @@ public static class ServiceCollectionExtensions
{
if (!globalSettings.SelfHosted && CoreHelpers.SettingHasValue(globalSettings.Events.ConnectionString))
{
services.AddKeyedSingleton<IEventWriteService, AzureQueueEventWriteService>("storage");
services.TryAddKeyedSingleton<IEventWriteService, AzureQueueEventWriteService>("storage");
if (CoreHelpers.SettingHasValue(globalSettings.EventLogging.AzureServiceBus.ConnectionString) &&
CoreHelpers.SettingHasValue(globalSettings.EventLogging.AzureServiceBus.EventTopicName))
{
services.AddSingleton<IEventIntegrationPublisher, AzureServiceBusService>();
services.AddKeyedSingleton<IEventWriteService, EventIntegrationEventWriteService>("broadcast");
services.TryAddSingleton<IEventIntegrationPublisher, AzureServiceBusService>();
services.TryAddKeyedSingleton<IEventWriteService, EventIntegrationEventWriteService>("broadcast");
}
else
{
services.AddKeyedSingleton<IEventWriteService, NoopEventWriteService>("broadcast");
services.TryAddKeyedSingleton<IEventWriteService, NoopEventWriteService>("broadcast");
}
}
else if (globalSettings.SelfHosted)
{
services.AddKeyedSingleton<IEventWriteService, RepositoryEventWriteService>("storage");
services.TryAddKeyedSingleton<IEventWriteService, RepositoryEventWriteService>("storage");
if (IsRabbitMqEnabled(globalSettings))
{
services.AddSingleton<IEventIntegrationPublisher, RabbitMqService>();
services.AddKeyedSingleton<IEventWriteService, EventIntegrationEventWriteService>("broadcast");
services.TryAddSingleton<IEventIntegrationPublisher, RabbitMqService>();
services.TryAddKeyedSingleton<IEventWriteService, EventIntegrationEventWriteService>("broadcast");
}
else
{
services.AddKeyedSingleton<IEventWriteService, NoopEventWriteService>("broadcast");
services.TryAddKeyedSingleton<IEventWriteService, NoopEventWriteService>("broadcast");
}
}
else
{
services.AddKeyedSingleton<IEventWriteService, NoopEventWriteService>("storage");
services.AddKeyedSingleton<IEventWriteService, NoopEventWriteService>("broadcast");
services.TryAddKeyedSingleton<IEventWriteService, NoopEventWriteService>("storage");
services.TryAddKeyedSingleton<IEventWriteService, NoopEventWriteService>("broadcast");
}
services.AddScoped<IEventWriteService, EventRouteService>();
return services;
}
private static IServiceCollection AddAzureServiceBusEventRepositoryListener(this IServiceCollection services, GlobalSettings globalSettings)
{
services.AddSingleton<IEventRepository, TableStorageRepos.EventRepository>();
services.AddSingleton<AzureTableStorageEventHandler>();
services.AddKeyedSingleton<IEventWriteService, RepositoryEventWriteService>("persistent");
services.AddSingleton<IHostedService>(provider =>
new AzureServiceBusEventListenerService(
handler: provider.GetRequiredService<AzureTableStorageEventHandler>(),
serviceBusService: provider.GetRequiredService<IAzureServiceBusService>(),
subscriptionName: globalSettings.EventLogging.AzureServiceBus.EventRepositorySubscriptionName,
globalSettings: globalSettings,
logger: provider.GetRequiredService<ILogger<AzureServiceBusEventListenerService>>()
)
);
return services;
}
private static IServiceCollection AddAzureServiceBusIntegration<TConfig, THandler>(
this IServiceCollection services,
string eventSubscriptionName,
string integrationSubscriptionName,
IntegrationType integrationType,
GlobalSettings globalSettings)
where TConfig : class
where THandler : class, IIntegrationHandler<TConfig>
{
var routingKey = integrationType.ToRoutingKey();
services.AddKeyedSingleton<IEventMessageHandler>(routingKey, (provider, _) =>
new EventIntegrationHandler<TConfig>(
integrationType,
provider.GetRequiredService<IEventIntegrationPublisher>(),
provider.GetRequiredService<IIntegrationFilterService>(),
provider.GetRequiredService<IIntegrationConfigurationDetailsCache>(),
provider.GetRequiredService<IUserRepository>(),
provider.GetRequiredService<IOrganizationRepository>(),
provider.GetRequiredService<ILogger<EventIntegrationHandler<TConfig>>>()));
services.AddSingleton<IHostedService>(provider =>
new AzureServiceBusEventListenerService(
handler: provider.GetRequiredKeyedService<IEventMessageHandler>(routingKey),
serviceBusService: provider.GetRequiredService<IAzureServiceBusService>(),
subscriptionName: eventSubscriptionName,
globalSettings: globalSettings,
logger: provider.GetRequiredService<ILogger<AzureServiceBusEventListenerService>>()
)
);
services.AddSingleton<IIntegrationHandler<TConfig>, THandler>();
services.AddSingleton<IHostedService>(provider =>
new AzureServiceBusIntegrationListenerService(
handler: provider.GetRequiredService<IIntegrationHandler<TConfig>>(),
topicName: globalSettings.EventLogging.AzureServiceBus.IntegrationTopicName,
subscriptionName: integrationSubscriptionName,
maxRetries: globalSettings.EventLogging.AzureServiceBus.MaxRetries,
serviceBusService: provider.GetRequiredService<IAzureServiceBusService>(),
logger: provider.GetRequiredService<ILogger<AzureServiceBusIntegrationListenerService>>()));
services.TryAddScoped<IEventWriteService, EventRouteService>();
return services;
}
public static IServiceCollection AddAzureServiceBusListeners(this IServiceCollection services, GlobalSettings globalSettings)
{
if (!CoreHelpers.SettingHasValue(globalSettings.EventLogging.AzureServiceBus.ConnectionString) ||
!CoreHelpers.SettingHasValue(globalSettings.EventLogging.AzureServiceBus.EventTopicName))
if (!IsAzureServiceBusEnabled(globalSettings))
{
return services;
}
services.AddSingleton<IntegrationConfigurationDetailsCacheService>();
services.AddSingleton<IIntegrationConfigurationDetailsCache>(provider =>
provider.GetRequiredService<IntegrationConfigurationDetailsCacheService>());
services.AddHostedService(provider => provider.GetRequiredService<IntegrationConfigurationDetailsCacheService>());
services.AddSingleton<IIntegrationFilterService, IntegrationFilterService>();
services.AddSingleton<IAzureServiceBusService, AzureServiceBusService>();
services.AddSingleton<IEventIntegrationPublisher, AzureServiceBusService>();
services.AddAzureServiceBusEventRepositoryListener(globalSettings);
services.TryAddSingleton<IAzureServiceBusService, AzureServiceBusService>();
services.TryAddSingleton<IEventIntegrationPublisher, AzureServiceBusService>();
services.TryAddSingleton<IEventRepository, TableStorageRepos.EventRepository>();
services.TryAddKeyedSingleton<IEventWriteService, RepositoryEventWriteService>("persistent");
services.TryAddSingleton<AzureTableStorageEventHandler>();
services.AddSlackService(globalSettings);
services.AddAzureServiceBusIntegration<SlackIntegrationConfigurationDetails, SlackIntegrationHandler>(
eventSubscriptionName: globalSettings.EventLogging.AzureServiceBus.SlackEventSubscriptionName,
integrationSubscriptionName: globalSettings.EventLogging.AzureServiceBus.SlackIntegrationSubscriptionName,
integrationType: IntegrationType.Slack,
globalSettings: globalSettings);
services.TryAddSingleton(TimeProvider.System);
services.AddHttpClient(WebhookIntegrationHandler.HttpClientName);
services.AddAzureServiceBusIntegration<WebhookIntegrationConfigurationDetails, WebhookIntegrationHandler>(
eventSubscriptionName: globalSettings.EventLogging.AzureServiceBus.WebhookEventSubscriptionName,
integrationSubscriptionName: globalSettings.EventLogging.AzureServiceBus.WebhookIntegrationSubscriptionName,
integrationType: IntegrationType.Webhook,
globalSettings: globalSettings);
services.AddAzureServiceBusIntegration<WebhookIntegrationConfigurationDetails, WebhookIntegrationHandler>(
eventSubscriptionName: globalSettings.EventLogging.AzureServiceBus.HecEventSubscriptionName,
integrationSubscriptionName: globalSettings.EventLogging.AzureServiceBus.HecIntegrationSubscriptionName,
integrationType: IntegrationType.Hec,
globalSettings: globalSettings);
return services;
}
private static IServiceCollection AddRabbitMqEventRepositoryListener(this IServiceCollection services, GlobalSettings globalSettings)
{
services.AddSingleton<EventRepositoryHandler>();
services.AddKeyedSingleton<IEventWriteService, RepositoryEventWriteService>("persistent");
services.AddSingleton<IHostedService>(provider =>
new RabbitMqEventListenerService(
provider.GetRequiredService<EventRepositoryHandler>(),
globalSettings.EventLogging.RabbitMq.EventRepositoryQueueName,
provider.GetRequiredService<IRabbitMqService>(),
provider.GetRequiredService<ILogger<RabbitMqEventListenerService>>()));
return services;
}
private static IServiceCollection AddRabbitMqIntegration<TConfig, THandler>(this IServiceCollection services,
string eventQueueName,
string integrationQueueName,
string integrationRetryQueueName,
int maxRetries,
IntegrationType integrationType)
where TConfig : class
where THandler : class, IIntegrationHandler<TConfig>
{
var routingKey = integrationType.ToRoutingKey();
services.AddKeyedSingleton<IEventMessageHandler>(routingKey, (provider, _) =>
new EventIntegrationHandler<TConfig>(
integrationType,
provider.GetRequiredService<IEventIntegrationPublisher>(),
provider.GetRequiredService<IIntegrationFilterService>(),
provider.GetRequiredService<IIntegrationConfigurationDetailsCache>(),
provider.GetRequiredService<IUserRepository>(),
provider.GetRequiredService<IOrganizationRepository>(),
provider.GetRequiredService<ILogger<EventIntegrationHandler<TConfig>>>()));
services.AddSingleton<IHostedService>(provider =>
new RabbitMqEventListenerService(
provider.GetRequiredKeyedService<IEventMessageHandler>(routingKey),
eventQueueName,
provider.GetRequiredService<IRabbitMqService>(),
provider.GetRequiredService<ILogger<RabbitMqEventListenerService>>()));
services.AddSingleton<IIntegrationHandler<TConfig>, THandler>();
services.AddSingleton<IHostedService>(provider =>
new RabbitMqIntegrationListenerService(
handler: provider.GetRequiredService<IIntegrationHandler<TConfig>>(),
routingKey: routingKey,
queueName: integrationQueueName,
retryQueueName: integrationRetryQueueName,
maxRetries: maxRetries,
rabbitMqService: provider.GetRequiredService<IRabbitMqService>(),
logger: provider.GetRequiredService<ILogger<RabbitMqIntegrationListenerService>>(),
timeProvider: provider.GetRequiredService<TimeProvider>()));
services.AddEventIntegrationServices(globalSettings);
return services;
}
@@ -753,49 +611,15 @@ public static class ServiceCollectionExtensions
return services;
}
services.AddSingleton<IntegrationConfigurationDetailsCacheService>();
services.AddSingleton<IIntegrationConfigurationDetailsCache>(provider =>
provider.GetRequiredService<IntegrationConfigurationDetailsCacheService>());
services.AddHostedService(provider => provider.GetRequiredService<IntegrationConfigurationDetailsCacheService>());
services.AddSingleton<IIntegrationFilterService, IntegrationFilterService>();
services.AddSingleton<IRabbitMqService, RabbitMqService>();
services.AddSingleton<IEventIntegrationPublisher, RabbitMqService>();
services.AddRabbitMqEventRepositoryListener(globalSettings);
services.TryAddSingleton<IRabbitMqService, RabbitMqService>();
services.TryAddSingleton<IEventIntegrationPublisher, RabbitMqService>();
services.TryAddSingleton<EventRepositoryHandler>();
services.AddSlackService(globalSettings);
services.AddRabbitMqIntegration<SlackIntegrationConfigurationDetails, SlackIntegrationHandler>(
globalSettings.EventLogging.RabbitMq.SlackEventsQueueName,
globalSettings.EventLogging.RabbitMq.SlackIntegrationQueueName,
globalSettings.EventLogging.RabbitMq.SlackIntegrationRetryQueueName,
globalSettings.EventLogging.RabbitMq.MaxRetries,
IntegrationType.Slack);
services.AddHttpClient(WebhookIntegrationHandler.HttpClientName);
services.AddRabbitMqIntegration<WebhookIntegrationConfigurationDetails, WebhookIntegrationHandler>(
globalSettings.EventLogging.RabbitMq.WebhookEventsQueueName,
globalSettings.EventLogging.RabbitMq.WebhookIntegrationQueueName,
globalSettings.EventLogging.RabbitMq.WebhookIntegrationRetryQueueName,
globalSettings.EventLogging.RabbitMq.MaxRetries,
IntegrationType.Webhook);
services.AddRabbitMqIntegration<WebhookIntegrationConfigurationDetails, WebhookIntegrationHandler>(
globalSettings.EventLogging.RabbitMq.HecEventsQueueName,
globalSettings.EventLogging.RabbitMq.HecIntegrationQueueName,
globalSettings.EventLogging.RabbitMq.HecIntegrationRetryQueueName,
globalSettings.EventLogging.RabbitMq.MaxRetries,
IntegrationType.Hec);
services.AddEventIntegrationServices(globalSettings);
return services;
}
private static bool IsRabbitMqEnabled(GlobalSettings settings)
{
return CoreHelpers.SettingHasValue(settings.EventLogging.RabbitMq.HostName) &&
CoreHelpers.SettingHasValue(settings.EventLogging.RabbitMq.Username) &&
CoreHelpers.SettingHasValue(settings.EventLogging.RabbitMq.Password) &&
CoreHelpers.SettingHasValue(settings.EventLogging.RabbitMq.EventExchangeName);
}
public static IServiceCollection AddSlackService(this IServiceCollection services, GlobalSettings globalSettings)
{
if (CoreHelpers.SettingHasValue(globalSettings.Slack.ClientId) &&
@@ -803,11 +627,11 @@ public static class ServiceCollectionExtensions
CoreHelpers.SettingHasValue(globalSettings.Slack.Scopes))
{
services.AddHttpClient(SlackService.HttpClientName);
services.AddSingleton<ISlackService, SlackService>();
services.TryAddSingleton<ISlackService, SlackService>();
}
else
{
services.AddSingleton<ISlackService, NoopSlackService>();
services.TryAddSingleton<ISlackService, NoopSlackService>();
}
return services;
@@ -1043,4 +867,161 @@ public static class ServiceCollectionExtensions
return (provider, connectionString);
}
private static IServiceCollection AddAzureServiceBusIntegration<TConfig, TListenerConfig>(this IServiceCollection services,
TListenerConfig listenerConfiguration)
where TConfig : class
where TListenerConfig : IIntegrationListenerConfiguration
{
services.TryAddKeyedSingleton<IEventMessageHandler>(serviceKey: listenerConfiguration.RoutingKey, implementationFactory: (provider, _) =>
new EventIntegrationHandler<TConfig>(
integrationType: listenerConfiguration.IntegrationType,
eventIntegrationPublisher: provider.GetRequiredService<IEventIntegrationPublisher>(),
integrationFilterService: provider.GetRequiredService<IIntegrationFilterService>(),
configurationCache: provider.GetRequiredService<IIntegrationConfigurationDetailsCache>(),
userRepository: provider.GetRequiredService<IUserRepository>(),
organizationRepository: provider.GetRequiredService<IOrganizationRepository>(),
logger: provider.GetRequiredService<ILogger<EventIntegrationHandler<TConfig>>>()
)
);
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService,
AzureServiceBusEventListenerService<TListenerConfig>>(provider =>
new AzureServiceBusEventListenerService<TListenerConfig>(
configuration: listenerConfiguration,
handler: provider.GetRequiredKeyedService<IEventMessageHandler>(serviceKey: listenerConfiguration.RoutingKey),
serviceBusService: provider.GetRequiredService<IAzureServiceBusService>(),
logger: provider.GetRequiredService<ILogger<AzureServiceBusEventListenerService<TListenerConfig>>>()
)
)
);
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService,
AzureServiceBusIntegrationListenerService<TListenerConfig>>(provider =>
new AzureServiceBusIntegrationListenerService<TListenerConfig>(
configuration: listenerConfiguration,
handler: provider.GetRequiredService<IIntegrationHandler<TConfig>>(),
serviceBusService: provider.GetRequiredService<IAzureServiceBusService>(),
logger: provider.GetRequiredService<ILogger<AzureServiceBusIntegrationListenerService<TListenerConfig>>>()
)
)
);
return services;
}
private static IServiceCollection AddEventIntegrationServices(this IServiceCollection services,
GlobalSettings globalSettings)
{
// Add common services
services.TryAddSingleton<IntegrationConfigurationDetailsCacheService>();
services.TryAddSingleton<IIntegrationConfigurationDetailsCache>(provider =>
provider.GetRequiredService<IntegrationConfigurationDetailsCacheService>());
services.AddHostedService(provider => provider.GetRequiredService<IntegrationConfigurationDetailsCacheService>());
services.TryAddSingleton<IIntegrationFilterService, IntegrationFilterService>();
services.TryAddKeyedSingleton<IEventWriteService, RepositoryEventWriteService>("persistent");
// Add services in support of handlers
services.AddSlackService(globalSettings);
services.TryAddSingleton(TimeProvider.System);
services.AddHttpClient(WebhookIntegrationHandler.HttpClientName);
// Add integration handlers
services.TryAddSingleton<IIntegrationHandler<SlackIntegrationConfigurationDetails>, SlackIntegrationHandler>();
services.TryAddSingleton<IIntegrationHandler<WebhookIntegrationConfigurationDetails>, WebhookIntegrationHandler>();
var repositoryConfiguration = new RepositoryListenerConfiguration(globalSettings);
var slackConfiguration = new SlackListenerConfiguration(globalSettings);
var webhookConfiguration = new WebhookListenerConfiguration(globalSettings);
var hecConfiguration = new HecListenerConfiguration(globalSettings);
if (IsRabbitMqEnabled(globalSettings))
{
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService,
RabbitMqEventListenerService<RepositoryListenerConfiguration>>(provider =>
new RabbitMqEventListenerService<RepositoryListenerConfiguration>(
handler: provider.GetRequiredService<EventRepositoryHandler>(),
configuration: repositoryConfiguration,
rabbitMqService: provider.GetRequiredService<IRabbitMqService>(),
logger: provider.GetRequiredService<ILogger<RabbitMqEventListenerService<RepositoryListenerConfiguration>>>()
)
)
);
services.AddRabbitMqIntegration<SlackIntegrationConfigurationDetails, SlackListenerConfiguration>(slackConfiguration);
services.AddRabbitMqIntegration<WebhookIntegrationConfigurationDetails, WebhookListenerConfiguration>(webhookConfiguration);
services.AddRabbitMqIntegration<WebhookIntegrationConfigurationDetails, HecListenerConfiguration>(hecConfiguration);
}
if (IsAzureServiceBusEnabled(globalSettings))
{
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService,
AzureServiceBusEventListenerService<RepositoryListenerConfiguration>>(provider =>
new AzureServiceBusEventListenerService<RepositoryListenerConfiguration>(
configuration: repositoryConfiguration,
handler: provider.GetRequiredService<AzureTableStorageEventHandler>(),
serviceBusService: provider.GetRequiredService<IAzureServiceBusService>(),
logger: provider.GetRequiredService<ILogger<AzureServiceBusEventListenerService<RepositoryListenerConfiguration>>>()
)
)
);
services.AddAzureServiceBusIntegration<SlackIntegrationConfigurationDetails, SlackListenerConfiguration>(slackConfiguration);
services.AddAzureServiceBusIntegration<WebhookIntegrationConfigurationDetails, WebhookListenerConfiguration>(webhookConfiguration);
services.AddAzureServiceBusIntegration<WebhookIntegrationConfigurationDetails, HecListenerConfiguration>(hecConfiguration);
}
return services;
}
private static IServiceCollection AddRabbitMqIntegration<TConfig, TListenerConfig>(this IServiceCollection services,
TListenerConfig listenerConfiguration)
where TConfig : class
where TListenerConfig : IIntegrationListenerConfiguration
{
services.TryAddKeyedSingleton<IEventMessageHandler>(serviceKey: listenerConfiguration.RoutingKey, implementationFactory: (provider, _) =>
new EventIntegrationHandler<TConfig>(
integrationType: listenerConfiguration.IntegrationType,
eventIntegrationPublisher: provider.GetRequiredService<IEventIntegrationPublisher>(),
integrationFilterService: provider.GetRequiredService<IIntegrationFilterService>(),
configurationCache: provider.GetRequiredService<IIntegrationConfigurationDetailsCache>(),
userRepository: provider.GetRequiredService<IUserRepository>(),
organizationRepository: provider.GetRequiredService<IOrganizationRepository>(),
logger: provider.GetRequiredService<ILogger<EventIntegrationHandler<TConfig>>>()
)
);
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService,
RabbitMqEventListenerService<TListenerConfig>>(provider =>
new RabbitMqEventListenerService<TListenerConfig>(
handler: provider.GetRequiredKeyedService<IEventMessageHandler>(serviceKey: listenerConfiguration.RoutingKey),
configuration: listenerConfiguration,
rabbitMqService: provider.GetRequiredService<IRabbitMqService>(),
logger: provider.GetRequiredService<ILogger<RabbitMqEventListenerService<TListenerConfig>>>()
)
)
);
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService,
RabbitMqIntegrationListenerService<TListenerConfig>>(provider =>
new RabbitMqIntegrationListenerService<TListenerConfig>(
handler: provider.GetRequiredService<IIntegrationHandler<TConfig>>(),
configuration: listenerConfiguration,
rabbitMqService: provider.GetRequiredService<IRabbitMqService>(),
logger: provider.GetRequiredService<ILogger<RabbitMqIntegrationListenerService<TListenerConfig>>>(),
timeProvider: provider.GetRequiredService<TimeProvider>()
)
)
);
return services;
}
private static bool IsAzureServiceBusEnabled(GlobalSettings settings)
{
return CoreHelpers.SettingHasValue(settings.EventLogging.AzureServiceBus.ConnectionString) &&
CoreHelpers.SettingHasValue(settings.EventLogging.AzureServiceBus.EventTopicName);
}
private static bool IsRabbitMqEnabled(GlobalSettings settings)
{
return CoreHelpers.SettingHasValue(settings.EventLogging.RabbitMq.HostName) &&
CoreHelpers.SettingHasValue(settings.EventLogging.RabbitMq.Username) &&
CoreHelpers.SettingHasValue(settings.EventLogging.RabbitMq.Password) &&
CoreHelpers.SettingHasValue(settings.EventLogging.RabbitMq.EventExchangeName);
}
}