Skip to content

Commit 3662206

Browse files
Copilotsamtrion
andcommitted
Add CouchDb health check implementation with tests
Co-authored-by: samtrion <[email protected]>
1 parent 2146e22 commit 3662206

16 files changed

+827
-0
lines changed

Directory.Packages.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.10" />
6464
<PackageVersion Include="Microsoft.Testing.Extensions.CodeCoverage" Version="18.1.0" />
6565
<PackageVersion Include="MongoDB.Driver" Version="3.5.0" />
66+
<PackageVersion Include="MyCouch" Version="7.6.0" />
6667
<PackageVersion Include="MySql.Data" Version="9.5.0" />
6768
<PackageVersion Include="MySqlConnector" Version="2.4.0" />
6869
<PackageVersion Include="Net.IBM.Data.Db2" Version="9.0.0.400" />
@@ -92,6 +93,7 @@
9293
<PackageVersion Include="Testcontainers.Azurite" Version="$(VersionTestContainers)" />
9394
<PackageVersion Include="Testcontainers.ClickHouse" Version="$(VersionTestContainers)" />
9495
<PackageVersion Include="Testcontainers.Consul" Version="$(VersionTestContainers)" />
96+
<PackageVersion Include="Testcontainers.CouchDb" Version="$(VersionTestContainers)" />
9597
<PackageVersion Include="Testcontainers.Db2" Version="$(VersionTestContainers)" />
9698
<PackageVersion Include="Testcontainers.Elasticsearch" Version="$(VersionTestContainers)" />
9799
<PackageVersion Include="Testcontainers.EventHubs" Version="$(VersionTestContainers)" />

HealthChecks.slnx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
<Project Path="src/NetEvolve.HealthChecks.Azure/NetEvolve.HealthChecks.Azure.csproj" />
4444
<Project Path="src/NetEvolve.HealthChecks.ClickHouse/NetEvolve.HealthChecks.ClickHouse.csproj" />
4545
<Project Path="src/NetEvolve.HealthChecks.Consul/NetEvolve.HealthChecks.Consul.csproj" />
46+
<Project Path="src/NetEvolve.HealthChecks.CouchDb/NetEvolve.HealthChecks.CouchDb.csproj" />
4647
<Project Path="src/NetEvolve.HealthChecks.Dapr/NetEvolve.HealthChecks.Dapr.csproj" />
4748
<Project Path="src/NetEvolve.HealthChecks.DB2/NetEvolve.HealthChecks.DB2.csproj" />
4849
<Project Path="src/NetEvolve.HealthChecks.DuckDB/NetEvolve.HealthChecks.DuckDB.csproj" />
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
namespace NetEvolve.HealthChecks.CouchDb;
2+
3+
using System.Threading;
4+
using Microsoft.Extensions.Configuration;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.Options;
7+
using static Microsoft.Extensions.Options.ValidateOptionsResult;
8+
9+
internal sealed class CouchDbConfigure : IConfigureNamedOptions<CouchDbOptions>, IValidateOptions<CouchDbOptions>
10+
{
11+
private readonly IConfiguration _configuration;
12+
13+
/// <summary>
14+
/// Initializes a new instance of the <see cref="CouchDbConfigure"/> class.
15+
/// </summary>
16+
/// <param name="configuration">The <see cref="IConfiguration"/> instance used to bind configuration values.</param>
17+
public CouchDbConfigure(IConfiguration configuration) => _configuration = configuration;
18+
19+
/// <inheritdoc />
20+
public void Configure(string? name, CouchDbOptions options)
21+
{
22+
ArgumentException.ThrowIfNullOrWhiteSpace(name);
23+
_configuration.Bind($"HealthChecks:CouchDb:{name}", options);
24+
}
25+
26+
/// <inheritdoc />
27+
public void Configure(CouchDbOptions options) => Configure(Options.DefaultName, options);
28+
29+
/// <inheritdoc />
30+
public ValidateOptionsResult Validate(string? name, CouchDbOptions options)
31+
{
32+
if (string.IsNullOrWhiteSpace(name))
33+
{
34+
return Fail("The name cannot be null or whitespace.");
35+
}
36+
37+
if (options is null)
38+
{
39+
return Fail("The option cannot be null.");
40+
}
41+
42+
if (string.IsNullOrWhiteSpace(options.ConnectionString))
43+
{
44+
return Fail("The connection string cannot be null or whitespace.");
45+
}
46+
47+
if (options.Timeout < Timeout.Infinite)
48+
{
49+
return Fail("The timeout value must be a positive number in milliseconds or -1 for an infinite timeout.");
50+
}
51+
52+
return Success;
53+
}
54+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
namespace NetEvolve.HealthChecks.CouchDb;
2+
3+
using System;
4+
using System.Diagnostics;
5+
using System.Threading.Tasks;
6+
using Microsoft.Extensions.Diagnostics.HealthChecks;
7+
using MyCouch;
8+
using SourceGenerator.Attributes;
9+
10+
[ConfigurableHealthCheck(typeof(CouchDbOptions))]
11+
internal sealed partial class CouchDbHealthCheck
12+
{
13+
private static async ValueTask<HealthCheckResult> ExecuteHealthCheckAsync(
14+
string name,
15+
#pragma warning disable S1172 // Unused method parameters should be removed
16+
HealthStatus failureStatus,
17+
#pragma warning restore S1172 // Unused method parameters should be removed
18+
CouchDbOptions options,
19+
CancellationToken cancellationToken
20+
)
21+
{
22+
var dbUri = new Uri(options.ConnectionString!);
23+
var connectionInfo = new DbConnectionInfo(
24+
dbUri.GetLeftPart(UriPartial.Authority),
25+
dbUri.AbsolutePath.Trim('/')
26+
);
27+
using var client = new MyCouchClient(connectionInfo);
28+
29+
var sw = Stopwatch.StartNew();
30+
await options.CommandAsync.Invoke(client, cancellationToken).ConfigureAwait(false);
31+
sw.Stop();
32+
33+
var isTimelyResponse = options.Timeout >= sw.Elapsed.TotalMilliseconds;
34+
35+
return HealthCheckState(isTimelyResponse, name);
36+
}
37+
38+
internal static async Task DefaultCommandAsync(MyCouchClient client, CancellationToken cancellationToken) =>
39+
_ = await client.Database.HeadAsync(cancellationToken).ConfigureAwait(false);
40+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
namespace NetEvolve.HealthChecks.CouchDb;
2+
3+
using MyCouch;
4+
5+
/// <summary>
6+
/// Options for <see cref="CouchDbHealthCheck"/>
7+
/// </summary>
8+
public sealed record CouchDbOptions
9+
{
10+
/// <summary>
11+
/// Gets or sets the connection string for the CouchDb server.
12+
/// </summary>
13+
public string? ConnectionString { get; set; }
14+
15+
/// <summary>
16+
/// The timeout to use when connecting and executing tasks against database.
17+
/// </summary>
18+
public int Timeout { get; set; } = 100;
19+
20+
/// <summary>
21+
/// The command to execute against the database.
22+
/// </summary>
23+
/// <remarks>For internal use only.</remarks>
24+
public Func<MyCouchClient, CancellationToken, Task> CommandAsync { get; internal set; } =
25+
CouchDbHealthCheck.DefaultCommandAsync;
26+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
namespace NetEvolve.HealthChecks.CouchDb;
2+
3+
using System;
4+
using System.Diagnostics.CodeAnalysis;
5+
using System.Linq;
6+
using Microsoft.Extensions.DependencyInjection;
7+
using Microsoft.Extensions.Diagnostics.HealthChecks;
8+
using SourceGenerator.Attributes;
9+
10+
/// <summary>
11+
/// Extensions methods for <see cref="IHealthChecksBuilder"/> with custom Health Checks.
12+
/// </summary>
13+
[HealthCheckHelper]
14+
public static partial class DependencyInjectionExtensions
15+
{
16+
private static readonly string[] _defaultTags = ["couchdb", "nosql"];
17+
18+
/// <summary>
19+
/// Add a health check for the CouchDb database.
20+
/// </summary>
21+
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
22+
/// <param name="name">The name of the <see cref="CouchDbHealthCheck"/>.</param>
23+
/// <param name="options">An optional action to configure.</param>
24+
/// <param name="tags">A list of additional tags that can be used to filter sets of health checks. Optional.</param>
25+
/// <exception cref="ArgumentNullException">The <paramref name="builder"/> is <see langword="null" />.</exception>
26+
/// <exception cref="ArgumentNullException">The <paramref name="name"/> is <see langword="null" />.</exception>
27+
/// <exception cref="ArgumentException">The <paramref name="name"/> is <see langword="null" /> or <c>whitespace</c>.</exception>
28+
/// <exception cref="ArgumentException">The <paramref name="name"/> is already in use.</exception>
29+
/// <exception cref="ArgumentNullException">The <paramref name="tags"/> is <see langword="null" />.</exception>
30+
public static IHealthChecksBuilder AddCouchDb(
31+
[NotNull] this IHealthChecksBuilder builder,
32+
[NotNull] string name,
33+
Action<CouchDbOptions>? options = null,
34+
params string[] tags
35+
)
36+
{
37+
ArgumentNullException.ThrowIfNull(builder);
38+
ArgumentException.ThrowIfNullOrEmpty(name);
39+
ArgumentNullException.ThrowIfNull(tags);
40+
41+
if (!builder.IsServiceTypeRegistered<CouchDbCheckMarker>())
42+
{
43+
_ = builder
44+
.Services.AddSingleton<CouchDbCheckMarker>()
45+
.AddSingleton<CouchDbHealthCheck>()
46+
.ConfigureOptions<CouchDbConfigure>();
47+
}
48+
49+
builder.ThrowIfNameIsAlreadyUsed<CouchDbHealthCheck>(name);
50+
51+
if (options is not null)
52+
{
53+
_ = builder.Services.Configure(name, options);
54+
}
55+
56+
return builder.AddCheck<CouchDbHealthCheck>(
57+
name,
58+
HealthStatus.Unhealthy,
59+
_defaultTags.Union(tags, StringComparer.OrdinalIgnoreCase)
60+
);
61+
}
62+
63+
private sealed partial class CouchDbCheckMarker;
64+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFrameworks>$(_ProjectTargetFrameworks)</TargetFrameworks>
4+
<Description>Contains HealthChecks for CouchDb, based on the nuget package `MyCouch`.</Description>
5+
<PackageTags>$(PackageTags);couchdb;nosql</PackageTags>
6+
</PropertyGroup>
7+
<ItemGroup>
8+
<PackageReference Include="MyCouch" />
9+
<PackageReference Include="NetEvolve.Extensions.Tasks" />
10+
</ItemGroup>
11+
<ItemGroup>
12+
<ProjectReference Include="..\SourceGenerator.Attributes\SourceGenerator.Attributes.csproj" PrivateAssets="all" />
13+
<ProjectReference
14+
Include="..\SourceGenerator.HealthChecks\SourceGenerator.HealthChecks.csproj"
15+
ReferenceOutputAssembly="false"
16+
OutputItemType="Analyzer"
17+
/>
18+
</ItemGroup>
19+
</Project>
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# NetEvolve.HealthChecks.CouchDb
2+
3+
[![NuGet](https://img.shields.io/nuget/v/NetEvolve.HealthChecks.CouchDb?logo=nuget)](https://www.nuget.org/packages/NetEvolve.HealthChecks.CouchDb/)
4+
[![NuGet](https://img.shields.io/nuget/dt/NetEvolve.HealthChecks.CouchDb?logo=nuget)](https://www.nuget.org/packages/NetEvolve.HealthChecks.CouchDb/)
5+
6+
This package provides a health check for CouchDb databases, based on the [MyCouch](https://www.nuget.org/packages/MyCouch/) package. The main purpose is to check if the database is available and if the database is online.
7+
8+
:bulb: This package is available for .NET 8.0 and later.
9+
10+
## Installation
11+
To use this package, you need to add the package to your project. You can do this by using the NuGet package manager or by using the dotnet CLI.
12+
```powershell
13+
dotnet add package NetEvolve.HealthChecks.CouchDb
14+
```
15+
16+
## Health Check - CouchDb Liveness
17+
The health check is a liveness check. It checks if the CouchDb Server is available and if the database is online.
18+
If the query needs longer than the configured timeout, the health check will return `Degraded`.
19+
If the query fails, for whatever reason, the health check will return `Unhealthy`.
20+
21+
### Usage
22+
After adding the package, you need to import the namespace and add the health check to the health check builder.
23+
```csharp
24+
using NetEvolve.HealthChecks.CouchDb;
25+
```
26+
Therefore, you can use two different approaches. In both approaches you have to provide a name for the health check.
27+
28+
### Parameters
29+
- `name`: The name of the health check. The name is used to identify the configuration object. It is required and must be unique within the application.
30+
- `options`: The configuration options for the health check. If you don't provide any options, the health check will use the configuration based approach.
31+
- `tags`: The tags for the health check. The tags `couchdb` and `nosql` are always used as default and combined with the user input. You can provide additional tags to group or filter the health checks.
32+
33+
### Variant 1: Configuration based
34+
The first one is to use the configuration based approach. This approach is recommended if you have multiple CouchDb instances to check.
35+
```csharp
36+
var builder = services.AddHealthChecks();
37+
38+
builder.AddCouchDb("<name>");
39+
```
40+
41+
The configuration looks like this:
42+
```json
43+
{
44+
..., // other configuration
45+
"HealthChecks": {
46+
"CouchDb": {
47+
"<name>": {
48+
"ConnectionString": "<connection string>",
49+
"Timeout": "<timeout>" // optional, default is 100 milliseconds
50+
}
51+
}
52+
}
53+
}
54+
```
55+
56+
### Variant 2: Builder based
57+
The second approach is to use the builder based approach. This approach is recommended if you only have one NoSQL Server instance to check or dynamic programmatic values.
58+
```csharp
59+
var builder = services.AddHealthChecks();
60+
61+
builder.AddCouchDb("<name>", options =>
62+
{
63+
options.ConnectionString = "<connection string>";
64+
options.Timeout = "<timeout>"; // optional, default is 100 milliseconds
65+
});
66+
```
67+
68+
### :bulb: You can always provide tags to all health checks, for grouping or filtering.
69+
70+
```csharp
71+
var builder = services.AddHealthChecks();
72+
73+
builder.AddCouchDb("<name>", options => ..., "CouchDb", "database");
74+
```
75+
76+
## License
77+
78+
This project is licensed under the MIT License - see the [LICENSE](https://raw.githubusercontent.com/dailydevops/healthchecks/refs/heads/main/LICENSE) file for details.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace NetEvolve.HealthChecks.Tests.Integration.CouchDb;
2+
3+
using System.Threading.Tasks;
4+
using Microsoft.Extensions.Logging.Abstractions;
5+
using Testcontainers.CouchDb;
6+
7+
public sealed class CouchDbDatabase : IAsyncInitializer, IAsyncDisposable
8+
{
9+
private readonly CouchDbContainer _database = new CouchDbBuilder().WithLogger(NullLogger.Instance).Build();
10+
11+
public string ConnectionString => _database.GetConnectionString();
12+
13+
public async ValueTask DisposeAsync() => await _database.DisposeAsync().ConfigureAwait(false);
14+
15+
public async Task InitializeAsync() => await _database.StartAsync().ConfigureAwait(false);
16+
}

0 commit comments

Comments
 (0)