Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
/>
</ItemGroup>
<ItemGroup>
<PackageVersion Include="Apache.NMS.ActiveMQ" Version="2.1.0" />
<PackageVersion Include="AWSSDK.SimpleNotificationService" Version="3.7.400.65" />
<PackageVersion Include="Azure.Data.Tables" Version="12.10.0" />
<PackageVersion Include="Azure.Identity" Version="1.13.2" />
<PackageVersion Include="Azure.Storage.Blobs" Version="12.24.0" />
Expand All @@ -48,9 +50,11 @@
<PackageVersion Include="StackExchange.Redis" Version="2.8.31" />
<PackageVersion Include="System.Data.SqlClient" Version="4.9.0" />
<PackageVersion Include="System.Text.Json" Version="9.0.4" />
<PackageVersion Include="Testcontainers.ActiveMq" Version="4.1.0" />
<PackageVersion Include="Testcontainers.Azurite" Version="4.4.0" />
<PackageVersion Include="Testcontainers.ClickHouse" Version="4.4.0" />
<PackageVersion Include="Testcontainers.Kafka" Version="4.4.0" />
<PackageVersion Include="Testcontainers.LocalStack" Version="4.4.0" />
<PackageVersion Include="Testcontainers.MsSql" Version="4.4.0" />
<PackageVersion Include="Testcontainers.MySql" Version="4.4.0" />
<PackageVersion Include="Testcontainers.Oracle" Version="4.4.0" />
Expand Down
29 changes: 29 additions & 0 deletions HealthChecks.sln
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEvolve.HealthChecks.Test
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEvolve.HealthChecks.Tests.Architecture", "tests\NetEvolve.HealthChecks.Tests.Architecture\NetEvolve.HealthChecks.Tests.Architecture.csproj", "{17BCA132-1FBB-46C1-B6A1-60F64969383D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEvolve.HealthChecks.Apache.ActiveMq", "src\NetEvolve.HealthChecks.Apache.ActiveMq\NetEvolve.HealthChecks.Apache.ActiveMq.csproj", "{1F26E911-E73F-409A-87FC-1214EF36FF61}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEvolve.HealthChecks.AWS.SNS", "src\NetEvolve.HealthChecks.AWS.SNS\NetEvolve.HealthChecks.AWS.SNS.csproj", "{546D5904-1811-457F-8C48-3F78D7F0C803}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -315,6 +318,30 @@ Global
{17BCA132-1FBB-46C1-B6A1-60F64969383D}.Release|x64.Build.0 = Release|Any CPU
{17BCA132-1FBB-46C1-B6A1-60F64969383D}.Release|x86.ActiveCfg = Release|Any CPU
{17BCA132-1FBB-46C1-B6A1-60F64969383D}.Release|x86.Build.0 = Release|Any CPU
{1F26E911-E73F-409A-87FC-1214EF36FF61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1F26E911-E73F-409A-87FC-1214EF36FF61}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1F26E911-E73F-409A-87FC-1214EF36FF61}.Debug|x64.ActiveCfg = Debug|Any CPU
{1F26E911-E73F-409A-87FC-1214EF36FF61}.Debug|x64.Build.0 = Debug|Any CPU
{1F26E911-E73F-409A-87FC-1214EF36FF61}.Debug|x86.ActiveCfg = Debug|Any CPU
{1F26E911-E73F-409A-87FC-1214EF36FF61}.Debug|x86.Build.0 = Debug|Any CPU
{1F26E911-E73F-409A-87FC-1214EF36FF61}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1F26E911-E73F-409A-87FC-1214EF36FF61}.Release|Any CPU.Build.0 = Release|Any CPU
{1F26E911-E73F-409A-87FC-1214EF36FF61}.Release|x64.ActiveCfg = Release|Any CPU
{1F26E911-E73F-409A-87FC-1214EF36FF61}.Release|x64.Build.0 = Release|Any CPU
{1F26E911-E73F-409A-87FC-1214EF36FF61}.Release|x86.ActiveCfg = Release|Any CPU
{1F26E911-E73F-409A-87FC-1214EF36FF61}.Release|x86.Build.0 = Release|Any CPU
{546D5904-1811-457F-8C48-3F78D7F0C803}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{546D5904-1811-457F-8C48-3F78D7F0C803}.Debug|Any CPU.Build.0 = Debug|Any CPU
{546D5904-1811-457F-8C48-3F78D7F0C803}.Debug|x64.ActiveCfg = Debug|Any CPU
{546D5904-1811-457F-8C48-3F78D7F0C803}.Debug|x64.Build.0 = Debug|Any CPU
{546D5904-1811-457F-8C48-3F78D7F0C803}.Debug|x86.ActiveCfg = Debug|Any CPU
{546D5904-1811-457F-8C48-3F78D7F0C803}.Debug|x86.Build.0 = Debug|Any CPU
{546D5904-1811-457F-8C48-3F78D7F0C803}.Release|Any CPU.ActiveCfg = Release|Any CPU
{546D5904-1811-457F-8C48-3F78D7F0C803}.Release|Any CPU.Build.0 = Release|Any CPU
{546D5904-1811-457F-8C48-3F78D7F0C803}.Release|x64.ActiveCfg = Release|Any CPU
{546D5904-1811-457F-8C48-3F78D7F0C803}.Release|x64.Build.0 = Release|Any CPU
{546D5904-1811-457F-8C48-3F78D7F0C803}.Release|x86.ActiveCfg = Release|Any CPU
{546D5904-1811-457F-8C48-3F78D7F0C803}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -340,6 +367,8 @@ Global
{66406BE8-0281-4C95-B90B-20CAE4516A16} = {E412EC77-2022-4A1D-AAC1-FDF1A4A45827}
{2B089420-E791-44E7-B471-F6F527B33E1C} = {E412EC77-2022-4A1D-AAC1-FDF1A4A45827}
{17BCA132-1FBB-46C1-B6A1-60F64969383D} = {E412EC77-2022-4A1D-AAC1-FDF1A4A45827}
{1F26E911-E73F-409A-87FC-1214EF36FF61} = {EF615D18-42E2-48A4-8EBA-E652DC574C56}
{546D5904-1811-457F-8C48-3F78D7F0C803} = {EF615D18-42E2-48A4-8EBA-E652DC574C56}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {28B4CC2B-39E8-49C0-9687-78121BD83A53}
Expand Down
6 changes: 6 additions & 0 deletions src/NetEvolve.HealthChecks.AWS.SNS/CreationMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace NetEvolve.HealthChecks.AWS.SNS;

public enum CreationMode
{
BasicAuthentication = 0,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
namespace NetEvolve.HealthChecks.AWS.SNS;

using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using NetEvolve.Arguments;
using NetEvolve.HealthChecks.Abstractions;

/// <summary>
/// Extensions methods for <see cref="IHealthChecksBuilder"/> with custom Health Checks.
/// </summary>
public static class DependencyInjectionExtensions
{
private static readonly string[] _defaultTags = ["aws", "sns", "message-queue"];

/// <summary>
/// Add a health check for AWS SimpleNotificationService (SNS).
/// </summary>
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
/// <param name="name">The name of the <see cref="SimpleNotificationServiceHealthCheck"/>.</param>
/// <param name="options">An optional action to configure.</param>
/// <param name="tags">A list of additional tags that can be used to filter sets of health checks. Optional.</param>
/// <exception cref="ArgumentNullException">The <paramref name="builder"/> is <see langword="null" />.</exception>
/// <exception cref="ArgumentNullException">The <paramref name="name"/> is <see langword="null" />.</exception>
/// <exception cref="ArgumentException">The <paramref name="name"/> is <see langword="null" /> or <c>whitespace</c>.</exception>
/// <exception cref="ArgumentException">The <paramref name="name"/> is already in use.</exception>
/// <exception cref="ArgumentNullException">The <paramref name="tags"/> is <see langword="null" />.</exception>
public static IHealthChecksBuilder AddSimpleNotificationService(
[NotNull] this IHealthChecksBuilder builder,
[NotNull] string name,
Action<SimpleNotificationServiceOptions>? options = null,
params string[] tags
)
{
ArgumentNullException.ThrowIfNull(builder);
Argument.ThrowIfNullOrEmpty(name);
ArgumentNullException.ThrowIfNull(tags);

if (!builder.IsServiceTypeRegistered<SimpleNotificationServiceCheckMarker>())
{
_ = builder
.Services.AddSingleton<SimpleNotificationServiceCheckMarker>()
.AddSingleton<SimpleNotificationServiceHealthCheck>()
.ConfigureOptions<SimpleNotificationServiceConfigure>();
}

if (builder.IsNameAlreadyUsed<SimpleNotificationServiceHealthCheck>(name))
{
throw new ArgumentException($"Name `{name}` already in use.", nameof(name), null);
}

if (options is not null)
{
_ = builder.Services.Configure(name, options);
}

return builder.AddCheck<SimpleNotificationServiceHealthCheck>(
name,
HealthStatus.Unhealthy,
_defaultTags.Union(tags, StringComparer.OrdinalIgnoreCase)
);
}

private sealed partial class SimpleNotificationServiceCheckMarker { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>$(_ProjectTargetFrameworks)</TargetFrameworks>

<Description>Contains HealthChecks for AWS Simple Notification Service (SNS).</Description>
<PackageTags>$(PackageTags);aws;sns;</PackageTags>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AWSSDK.SimpleNotificationService" />
<PackageReference Include="NetEvolve.Extensions.Tasks" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\NetEvolve.HealthChecks.Abstractions\NetEvolve.HealthChecks.Abstractions.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace NetEvolve.HealthChecks.AWS.SNS;

using Microsoft.Extensions.Options;

internal sealed class SimpleNotificationServiceConfigure
: IConfigureNamedOptions<SimpleNotificationServiceOptions>,
IValidateOptions<SimpleNotificationServiceOptions>
{
public void Configure(string? name, SimpleNotificationServiceOptions options) { }

public void Configure(SimpleNotificationServiceOptions options) { }

public ValidateOptionsResult Validate(string? name, SimpleNotificationServiceOptions options) =>
ValidateOptionsResult.Success;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
namespace NetEvolve.HealthChecks.AWS.SNS;

using System;
using System.Threading;
using System.Threading.Tasks;
using Amazon;
using Amazon.SimpleNotificationService;
using Amazon.SimpleNotificationService.Model;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Options;
using NetEvolve.Extensions.Tasks;
using NetEvolve.HealthChecks.Abstractions;

internal sealed class SimpleNotificationServiceHealthCheck
: ConfigurableHealthCheckBase<SimpleNotificationServiceOptions>
{
public SimpleNotificationServiceHealthCheck(
IOptionsMonitor<SimpleNotificationServiceOptions> optionsMonitor
)
: base(optionsMonitor) { }

protected override async ValueTask<HealthCheckResult> ExecuteHealthCheckAsync(
string name,
HealthStatus failureStatus,
SimpleNotificationServiceOptions options,
CancellationToken cancellationToken
)
{
using var client = CreateClient(options);

var (isValid, topic) = await client
.GetSubscriptionAttributesAsync(
new GetSubscriptionAttributesRequest { SubscriptionArn = options.TopicName },
cancellationToken
)
.WithTimeoutAsync(options.Timeout, cancellationToken)
.ConfigureAwait(false);

return HealthCheckState(isValid && topic is not null, name);
}

private static AmazonSimpleNotificationServiceClient CreateClient(
SimpleNotificationServiceOptions options
)
{
var hasCredentials = options.GetCredentials() is not null;
var hasEndpoint = options.GetRegionEndpoint() is not null;

var config = new AmazonSimpleNotificationServiceConfig
{
ServiceURL = options.ServiceUrl,
RegionEndpoint = RegionEndpoint.USEast1,
};

return (hasCredentials, hasEndpoint) switch
{
(true, true) => new AmazonSimpleNotificationServiceClient(
options.GetCredentials(),
options.GetRegionEndpoint()
),
(true, false) => new AmazonSimpleNotificationServiceClient(
options.GetCredentials(),
config
),
(false, true) => new AmazonSimpleNotificationServiceClient(options.GetRegionEndpoint()),
_ => throw new InvalidOperationException("Invalid ClientCreationMode."),
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace NetEvolve.HealthChecks.AWS.SNS;

using System;
using Amazon;
using Amazon.Runtime;

public sealed class SimpleNotificationServiceOptions
{
public string? AccessKey { get; set; }

public CreationMode CreationMode { get; set; }

public string? SecretKey { get; set; }

#pragma warning disable CA1056 // URI-like properties should not be strings
public string? ServiceUrl { get; set; }
#pragma warning restore CA1056 // URI-like properties should not be strings

public string? TopicName { get; set; }

public int Timeout { get; set; } = 100;

internal AWSCredentials? GetCredentials()
{
return CreationMode switch
{
CreationMode.BasicAuthentication => new BasicAWSCredentials(AccessKey, SecretKey),
_ => null,
};
}

internal RegionEndpoint? GetRegionEndpoint() => CreationMode == CreationMode ? null : null;
}
43 changes: 43 additions & 0 deletions src/NetEvolve.HealthChecks.Apache.ActiveMq/ActiveMqConfigure.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
namespace NetEvolve.HealthChecks.Apache.ActiveMq;

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using NetEvolve.Arguments;
using static Microsoft.Extensions.Options.ValidateOptionsResult;

internal sealed class ActiveMqConfigure
: IConfigureNamedOptions<ActiveMqOptions>,
IValidateOptions<ActiveMqOptions>
{
private readonly IConfiguration _configuration;

public ActiveMqConfigure(IConfiguration configuration) => _configuration = configuration;

public void Configure(string? name, ActiveMqOptions options)
{
Argument.ThrowIfNullOrWhiteSpace(name);
_configuration.Bind($"HealthChecks:ActiveMq:{name}", options);
}

public void Configure(ActiveMqOptions options) => Configure(Options.DefaultName, options);

public ValidateOptionsResult Validate(string? name, ActiveMqOptions options)
{
if (string.IsNullOrWhiteSpace(name))
{
return Fail("The name cannot be null or whitespace.");
}

if (options is null)
{
return Fail("The option cannot be null.");
}

if (string.IsNullOrWhiteSpace(options.BrokerAddress))
{
return Fail("The broker address cannot be null or whitespace.");
}

return Success;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
namespace NetEvolve.HealthChecks.Apache.ActiveMq;

using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using global::Apache.NMS;
using global::Apache.NMS.ActiveMQ;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Options;
using NetEvolve.Extensions.Tasks;
using NetEvolve.HealthChecks.Abstractions;

internal sealed class ActiveMqHealthCheck : ConfigurableHealthCheckBase<ActiveMqOptions>
{
public ActiveMqHealthCheck(IOptionsMonitor<ActiveMqOptions> optionsMonitor)
: base(optionsMonitor) { }

protected override async ValueTask<HealthCheckResult> ExecuteHealthCheckAsync(
string name,
HealthStatus failureStatus,
ActiveMqOptions options,
CancellationToken cancellationToken
)
{
using var client = await ClientFactory
.GetConnectionAsync(options, cancellationToken)
.ConfigureAwait(false);

var isValid = await client
.StartAsync()
.WithTimeoutAsync(options.Timeout, cancellationToken)
.ConfigureAwait(false);

return HealthCheckState(client.IsStarted && isValid, name);
}
}
Loading
Loading