1
0
mirror of https://github.com/bitwarden/server synced 2025-12-21 18:53:41 +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

@@ -2,27 +2,26 @@
using System.Text;
using Azure.Messaging.ServiceBus;
using Bit.Core.Settings;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Microsoft.Extensions.Logging;
namespace Bit.Core.Services;
public class AzureServiceBusEventListenerService : EventLoggingListenerService
public class AzureServiceBusEventListenerService<TConfiguration> : EventLoggingListenerService
where TConfiguration : IEventListenerConfiguration
{
private readonly ServiceBusProcessor _processor;
public AzureServiceBusEventListenerService(
TConfiguration configuration,
IEventMessageHandler handler,
IAzureServiceBusService serviceBusService,
string subscriptionName,
GlobalSettings globalSettings,
ILogger<AzureServiceBusEventListenerService> logger) : base(handler, logger)
ILogger<AzureServiceBusEventListenerService<TConfiguration>> logger) : base(handler, logger)
{
_processor = serviceBusService.CreateProcessor(
globalSettings.EventLogging.AzureServiceBus.EventTopicName,
subscriptionName,
topicName: configuration.EventTopicName,
subscriptionName: configuration.EventSubscriptionName,
new ServiceBusProcessorOptions());
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)

View File

@@ -1,32 +1,36 @@
#nullable enable
using Azure.Messaging.ServiceBus;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace Bit.Core.Services;
public class AzureServiceBusIntegrationListenerService : BackgroundService
public class AzureServiceBusIntegrationListenerService<TConfiguration> : BackgroundService
where TConfiguration : IIntegrationListenerConfiguration
{
private readonly int _maxRetries;
private readonly IAzureServiceBusService _serviceBusService;
private readonly IIntegrationHandler _handler;
private readonly ServiceBusProcessor _processor;
private readonly ILogger<AzureServiceBusIntegrationListenerService> _logger;
private readonly ILogger<AzureServiceBusIntegrationListenerService<TConfiguration>> _logger;
public AzureServiceBusIntegrationListenerService(IIntegrationHandler handler,
string topicName,
string subscriptionName,
int maxRetries,
public AzureServiceBusIntegrationListenerService(
TConfiguration configuration,
IIntegrationHandler handler,
IAzureServiceBusService serviceBusService,
ILogger<AzureServiceBusIntegrationListenerService> logger)
ILogger<AzureServiceBusIntegrationListenerService<TConfiguration>> logger)
{
_handler = handler;
_logger = logger;
_maxRetries = maxRetries;
_maxRetries = configuration.MaxRetries;
_serviceBusService = serviceBusService;
_processor = _serviceBusService.CreateProcessor(topicName, subscriptionName, new ServiceBusProcessorOptions());
_processor = _serviceBusService.CreateProcessor(
topicName: configuration.IntegrationTopicName,
subscriptionName: configuration.IntegrationSubscriptionName,
options: new ServiceBusProcessorOptions());
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)

View File

@@ -399,35 +399,44 @@ These names added here are what must match the values provided in the secrets or
in Global Settings. This must be in place (and the local ASB emulator restarted) before you can use any
code locally that accesses ASB resources.
## ListenerConfiguration
New integrations will need their own subclass of `ListenerConfiguration` which also conforms to
`IIntegrationListenerConfiguration`. This class provides a way of accessing the previously configured
RabbitMQ queues and ASB subscriptions by referring to the values created in `GlobalSettings`. This new
listener configuration will be used to type the listener and provide the means to access the necessary
configurations for the integration.
## ServiceCollectionExtensions
In our `ServiceCollectionExtensions`, we pull all the above pieces together to start listeners on each message
tier with handlers to process the integration. There are a number of helper methods in here to make this simple
to add a new integration - one call per platform.
tier with handlers to process the integration.
Also note that if an integration needs a custom singleton / service defined, the add listeners method is a
good place to set that up. For instance, `SlackIntegrationHandler` needs a `SlackService`, so the singleton
declaration is right above the add integration method for slack. Same thing for webhooks when it comes to
defining a custom HttpClient by name.
The core method for all event integration setup is `AddEventIntegrationServices`. This method is called by
both of the add listeners methods, which ensures that we have one common place to set up cross-messaging-platform
dependencies and integrations. For instance, `SlackIntegrationHandler` needs a `SlackService`, so
`AddEventIntegrationServices` has a call to `AddSlackService`. Same thing for webhooks when it
comes to defining a custom HttpClient by name.
1. In `AddEventIntegrationServices` create the listener configuration:
1. In `AddRabbitMqListeners` add the integration:
``` csharp
services.AddRabbitMqIntegration<ExampleIntegrationConfigurationDetails, ExampleIntegrationHandler>(
globalSettings.EventLogging.RabbitMq.ExampleEventsQueueName,
globalSettings.EventLogging.RabbitMq.ExampleIntegrationQueueName,
globalSettings.EventLogging.RabbitMq.ExampleIntegrationRetryQueueName,
globalSettings.EventLogging.RabbitMq.MaxRetries,
IntegrationType.Example);
var exampleConfiguration = new ExampleListenerConfiguration(globalSettings);
```
2. In `AddAzureServiceBusListeners` add the integration:
2. Add the integration to both the RabbitMQ and ASB specific declarations:
``` csharp
services.AddAzureServiceBusIntegration<ExampleIntegrationConfigurationDetails, ExampleIntegrationHandler>(
eventSubscriptionName: globalSettings.EventLogging.AzureServiceBus.ExampleEventSubscriptionName,
integrationSubscriptionName: globalSettings.EventLogging.AzureServiceBus.ExampleIntegrationSubscriptionName,
integrationType: IntegrationType.Example,
globalSettings: globalSettings);
services.AddRabbitMqIntegration<ExampleIntegrationConfigurationDetails, ExampleListenerConfiguration>(exampleConfiguration);
```
and
``` csharp
services.AddAzureServiceBusIntegration<ExampleIntegrationConfigurationDetails, ExampleListenerConfiguration>(exampleConfiguration);
```
# Deploying a new integration
## RabbitMQ

View File

@@ -1,13 +1,15 @@
#nullable enable
using System.Text;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Microsoft.Extensions.Logging;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
namespace Bit.Core.Services;
public class RabbitMqEventListenerService : EventLoggingListenerService
public class RabbitMqEventListenerService<TConfiguration> : EventLoggingListenerService
where TConfiguration : IEventListenerConfiguration
{
private readonly Lazy<Task<IChannel>> _lazyChannel;
private readonly string _queueName;
@@ -15,12 +17,11 @@ public class RabbitMqEventListenerService : EventLoggingListenerService
public RabbitMqEventListenerService(
IEventMessageHandler handler,
string queueName,
TConfiguration configuration,
IRabbitMqService rabbitMqService,
ILogger<RabbitMqEventListenerService> logger) : base(handler, logger)
ILogger<RabbitMqEventListenerService<TConfiguration>> logger) : base(handler, logger)
{
_logger = logger;
_queueName = queueName;
_queueName = configuration.EventQueueName;
_rabbitMqService = rabbitMqService;
_lazyChannel = new Lazy<Task<IChannel>>(() => _rabbitMqService.CreateChannelAsync());
}

View File

@@ -10,7 +10,8 @@ using RabbitMQ.Client.Events;
namespace Bit.Core.Services;
public class RabbitMqIntegrationListenerService : BackgroundService
public class RabbitMqIntegrationListenerService<TConfiguration> : BackgroundService
where TConfiguration : IIntegrationListenerConfiguration
{
private readonly int _maxRetries;
private readonly string _queueName;
@@ -19,26 +20,24 @@ public class RabbitMqIntegrationListenerService : BackgroundService
private readonly IIntegrationHandler _handler;
private readonly Lazy<Task<IChannel>> _lazyChannel;
private readonly IRabbitMqService _rabbitMqService;
private readonly ILogger<RabbitMqIntegrationListenerService> _logger;
private readonly ILogger<RabbitMqIntegrationListenerService<TConfiguration>> _logger;
private readonly TimeProvider _timeProvider;
public RabbitMqIntegrationListenerService(IIntegrationHandler handler,
string routingKey,
string queueName,
string retryQueueName,
int maxRetries,
public RabbitMqIntegrationListenerService(
IIntegrationHandler handler,
TConfiguration configuration,
IRabbitMqService rabbitMqService,
ILogger<RabbitMqIntegrationListenerService> logger,
ILogger<RabbitMqIntegrationListenerService<TConfiguration>> logger,
TimeProvider timeProvider)
{
_handler = handler;
_routingKey = routingKey;
_retryQueueName = retryQueueName;
_queueName = queueName;
_maxRetries = configuration.MaxRetries;
_routingKey = configuration.RoutingKey;
_retryQueueName = configuration.IntegrationRetryQueueName;
_queueName = configuration.IntegrationQueueName;
_rabbitMqService = rabbitMqService;
_logger = logger;
_timeProvider = timeProvider;
_maxRetries = maxRetries;
_lazyChannel = new Lazy<Task<IChannel>>(() => _rabbitMqService.CreateChannelAsync());
}