-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Refactor configuration for azure queue service for events to include queue name #6724
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
2bc0ea9
c20f8d3
3054b20
aaf8d48
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,6 +33,10 @@ | |
| "id": "<your Installation Id>", | ||
| "key": "<your Installation Key>" | ||
| }, | ||
| "events": { | ||
| "connectionString": "", | ||
| "queueName": "event" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ Documentation suggestion: While the example shows
Consider adding a comment: // Azure Queue Storage configuration for events
// Leave connectionString and queueName empty to disable the service
// Default queue name "event" is used for local development
"events": {
"connectionString": "",
"queueName": "event"
}, |
||
| }, | ||
| "licenseDirectory": "<full path to license directory>", | ||
| "enableNewDeviceVerification": true, | ||
| "enableEmailVerification": true | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -91,7 +91,8 @@ public static IServiceCollection AddEventWriteServices(this IServiceCollection s | |
| return services; | ||
| } | ||
|
|
||
| if (CoreHelpers.SettingHasValue(globalSettings.Events.ConnectionString)) | ||
| if (CoreHelpers.SettingHasValue(globalSettings.Events.ConnectionString) && | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ Correctly Addresses Previous Review Finding #2 This now checks both The check aligns with the documentation in the XML comments (lines 69-70) and the read-side implementation in |
||
| CoreHelpers.SettingHasValue(globalSettings.Events.QueueName)) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing test coverage: While the implementation correctly validates both Consider adding test cases like:
This would ensure the validation logic works correctly and prevent regression if someone changes the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. โ Missing test coverage for partial configuration: The check validates both Recommended test cases: [Fact]
public void AddEventWriteServices_ConnectionStringPresentButQueueNameMissing_RegistersNoopService()
{
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
{
["GlobalSettings:Events:ConnectionString"] = "DefaultEndpoints...",
["GlobalSettings:Events:QueueName"] = null
});
services.AddEventWriteServices(globalSettings);
Assert.Contains(services, s => s.ServiceType == typeof(IEventWriteService)
&& s.ImplementationType == typeof(NoopEventWriteService));
}
[Fact]
public void AddEventWriteServices_QueueNamePresentButConnectionStringMissing_RegistersNoopService()
{
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
{
["GlobalSettings:Events:ConnectionString"] = null,
["GlobalSettings:Events:QueueName"] = "event"
});
services.AddEventWriteServices(globalSettings);
Assert.Contains(services, s => s.ServiceType == typeof(IEventWriteService)
&& s.ImplementationType == typeof(NoopEventWriteService));
}This ensures the |
||
| { | ||
| services.TryAddSingleton<IEventWriteService, AzureQueueEventWriteService>(); | ||
| return services; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,7 +8,7 @@ namespace Bit.Core.Services; | |
| public class AzureQueueEventWriteService : AzureQueueService<IEvent>, IEventWriteService | ||
| { | ||
| public AzureQueueEventWriteService(GlobalSettings globalSettings) : base( | ||
| new QueueClient(globalSettings.Events.ConnectionString, "event"), | ||
| new QueueClient(globalSettings.Events.ConnectionString, globalSettings.Events.QueueName), | ||
brant-livefront marked this conversation as resolved.
Show resolved
Hide resolved
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. โ Potential Null Reference Exception If While the registration in Recommendation: Add a guard clause or null check with a more descriptive error message: public AzureQueueEventWriteService(GlobalSettings globalSettings) : base(
new QueueClient(
globalSettings.Events.ConnectionString ?? throw new ArgumentNullException(nameof(globalSettings.Events.ConnectionString)),
globalSettings.Events.QueueName ?? throw new ArgumentNullException(nameof(globalSettings.Events.QueueName))
),
JsonHelpers.IgnoreWritingNull)
{ }This would make debugging easier by clearly identifying which parameter is null.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential runtime exception if QueueName is null: While Consider adding a guard clause for defensive programming: public AzureQueueEventWriteService(GlobalSettings globalSettings) : base(
new QueueClient(
globalSettings.Events.ConnectionString
?? throw new ArgumentNullException(nameof(globalSettings), "Events.ConnectionString cannot be null"),
globalSettings.Events.QueueName
?? throw new ArgumentNullException(nameof(globalSettings), "Events.QueueName cannot be null")),
JsonHelpers.IgnoreWritingNull)
{ }This provides better error messages and fail-fast behavior if the service is misconfigured.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider adding guard clauses for better error messages: public AzureQueueEventWriteService(GlobalSettings globalSettings) : base(
new QueueClient(
globalSettings.Events.ConnectionString
?? throw new ArgumentNullException(nameof(globalSettings), "Events.ConnectionString cannot be null"),
globalSettings.Events.QueueName
?? throw new ArgumentNullException(nameof(globalSettings), "Events.QueueName cannot be null")),
JsonHelpers.IgnoreWritingNull)
{ }This provides fail-fast behavior with clear error messages if misconfigured. |
||
| JsonHelpers.IgnoreWritingNull) | ||
| { } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,7 +56,7 @@ | |
| public virtual EventLoggingSettings EventLogging { get; set; } = new EventLoggingSettings(); | ||
| public virtual MailSettings Mail { get; set; } = new MailSettings(); | ||
| public virtual IConnectionStringSettings Storage { get; set; } = new ConnectionStringSettings(); | ||
| public virtual ConnectionStringSettings Events { get; set; } = new ConnectionStringSettings(); | ||
| public virtual AzureQueueEventSettings Events { get; set; } = new AzureQueueEventSettings(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Backward compatibility concern: Changing from "events": {
"connectionString": "SECRET"
}They don't have the new
Recommendations:
|
||
| public virtual DistributedCacheSettings DistributedCache { get; set; } = new DistributedCacheSettings(); | ||
| public virtual NotificationsSettings Notifications { get; set; } = new NotificationsSettings(); | ||
| public virtual IFileStorageSettings Attachment { get; set; } | ||
|
|
@@ -116,7 +116,7 @@ | |
| { | ||
| return null; | ||
| } | ||
| return string.Format("http://{0}:5000", name); | ||
| } | ||
|
|
||
| public string BuildDirectory(string explicitValue, string appendedPath) | ||
|
|
@@ -395,6 +395,24 @@ | |
| } | ||
| } | ||
|
|
||
| public class AzureQueueEventSettings : IConnectionStringSettings | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ Note: Inconsistent Null Handling Pattern in Codebase The new However, most other settings classes in this file use For this PR: The null-safe approach here is correct and aligns with the intentional design of allowing null QueueName to disable the service. For future consideration: The codebase has an inconsistent pattern that could be standardized in a separate refactoring PR. |
||
| { | ||
| private string _connectionString; | ||
| private string _queueName; | ||
|
|
||
| public string ConnectionString | ||
| { | ||
| get => _connectionString; | ||
| set => _connectionString = value?.Trim('"'); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ Good Null Safety Implementation The null-conditional operator ( This is consistent with the pattern used in other settings classes like
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good null safety: The null-conditional operator While this isn't a blocker for this PR, consider filing a follow-up issue to address the null safety inconsistency in |
||
| } | ||
|
|
||
| public string QueueName | ||
| { | ||
| get => _queueName; | ||
| set => _queueName = value?.Trim('"'); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ Good null safety: The null-conditional operator This is consistent with patterns used elsewhere in the codebase like |
||
| } | ||
| } | ||
|
|
||
| public class ConnectionStringSettings : IConnectionStringSettings | ||
| { | ||
| private string _connectionString; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,14 +6,15 @@ | |
| using Bit.Core; | ||
| using Bit.Core.Models.Data; | ||
| using Bit.Core.Services; | ||
| using Bit.Core.Settings; | ||
| using Bit.Core.Utilities; | ||
|
|
||
| namespace Bit.EventsProcessor; | ||
|
|
||
| public class AzureQueueHostedService : IHostedService, IDisposable | ||
| { | ||
| private readonly ILogger<AzureQueueHostedService> _logger; | ||
| private readonly IConfiguration _configuration; | ||
| private readonly GlobalSettings _globalSettings; | ||
|
|
||
| private Task _executingTask; | ||
| private CancellationTokenSource _cts; | ||
|
|
@@ -22,10 +23,10 @@ public class AzureQueueHostedService : IHostedService, IDisposable | |
|
|
||
| public AzureQueueHostedService( | ||
| ILogger<AzureQueueHostedService> logger, | ||
| IConfiguration configuration) | ||
| GlobalSettings globalSettings) | ||
| { | ||
| _logger = logger; | ||
| _configuration = configuration; | ||
| _globalSettings = globalSettings; | ||
| } | ||
|
|
||
| public Task StartAsync(CancellationToken cancellationToken) | ||
|
|
@@ -56,11 +57,12 @@ public void Dispose() | |
|
|
||
| private async Task ExecuteAsync(CancellationToken cancellationToken) | ||
| { | ||
| var storageConnectionString = _configuration["azureStorageConnectionString"]; | ||
| var queueName = _configuration["azureQueueServiceQueueName"]; | ||
| var storageConnectionString = _globalSettings.Events.ConnectionString; | ||
| var queueName = _globalSettings.Events.QueueName; | ||
| if (string.IsNullOrWhiteSpace(storageConnectionString) || | ||
| string.IsNullOrWhiteSpace(queueName)) | ||
| { | ||
| _logger.LogInformation("Azure Queue Hosted Service is disabled. Missing connection string or queue name."); | ||
brant-livefront marked this conversation as resolved.
Show resolved
Hide resolved
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ Excellent Error Handling The information-level log message clearly indicates why the service is disabled. This is much better than the previous silent failure. Minor suggestion: Consider using structured logging to make this more queryable: _logger.LogInformation("Azure Queue Hosted Service is disabled. ConnectionStringPresent: {ConnectionStringPresent}, QueueNamePresent: {QueueNamePresent}",
!string.IsNullOrWhiteSpace(storageConnectionString),
!string.IsNullOrWhiteSpace(queueName));This would help operators understand exactly which configuration is missing without needing to look at code.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good improvement: This log message is helpful for troubleshooting configuration issues. The service now gracefully handles missing configuration and logs appropriately. Minor suggestion: Consider using structured logging with named parameters for better log aggregation and filtering: _logger.LogInformation("Azure Queue Hosted Service is disabled. ConnectionString present: {HasConnectionString}, QueueName present: {HasQueueName}",
!string.IsNullOrWhiteSpace(storageConnectionString),
!string.IsNullOrWhiteSpace(queueName));This would make it easier to diagnose which specific configuration value is missing in production environments.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ Excellent error handling: The information-level log message properly addresses Finding #3 from the previous review. This makes troubleshooting much easier. Minor enhancement suggestion (optional): Consider using structured logging to make this more queryable in production: _logger.LogInformation(
"Azure Queue Hosted Service is disabled. ConnectionString present: {HasConnectionString}, QueueName present: {HasQueueName}",
!string.IsNullOrWhiteSpace(storageConnectionString),
!string.IsNullOrWhiteSpace(queueName));This would help operators quickly identify which specific configuration value is missing without inspecting code. |
||
| return; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -727,7 +727,8 @@ public void AddEventWriteServices_EventsConnectionStringPresent_RegistersAzureQu | |
| var services = new ServiceCollection(); | ||
| var globalSettings = CreateGlobalSettings(new Dictionary<string, string?> | ||
| { | ||
| ["GlobalSettings:Events:ConnectionString"] = "DefaultEndpointsProtocol=https;AccountName=test;AccountKey=test;EndpointSuffix=core.windows.net" | ||
| ["GlobalSettings:Events:ConnectionString"] = "DefaultEndpointsProtocol=https;AccountName=test;AccountKey=test;EndpointSuffix=core.windows.net", | ||
| ["GlobalSettings:Events:QueueName"] = "event" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ Missing Test Coverage The author mentioned adding "a test to prevent service start if QueueName is null/empty", but I cannot find such a test in this file. There should be tests for these scenarios:
Recommendation: Add these test cases: [Fact]
public void AddEventWriteServices_EventsConnectionStringPresentButQueueNameMissing_RegistersNoopService()
{
var services = new ServiceCollection();
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
{
["GlobalSettings:Events:ConnectionString"] = "DefaultEndpointsProtocol=https;AccountName=test;AccountKey=test;EndpointSuffix=core.windows.net",
["GlobalSettings:Events:QueueName"] = null
});
services.AddEventWriteServices(globalSettings);
Assert.Contains(services, s => s.ServiceType == typeof(IEventWriteService) && s.ImplementationType == typeof(NoopEventWriteService));
}
[Fact]
public void AddEventWriteServices_QueueNamePresentButConnectionStringMissing_RegistersNoopService()
{
var services = new ServiceCollection();
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
{
["GlobalSettings:Events:ConnectionString"] = null,
["GlobalSettings:Events:QueueName"] = "event"
});
services.AddEventWriteServices(globalSettings);
Assert.Contains(services, s => s.ServiceType == typeof(IEventWriteService) && s.ImplementationType == typeof(NoopEventWriteService));
} |
||
| }); | ||
|
|
||
| services.AddEventWriteServices(globalSettings); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documentation needed: The example shows
queueName: "event"as the default value, but it's not clear:Consider adding a comment above this section:
This would help developers understand the configuration's purpose and behavior.