Skip to content

Commit 225dc0b

Browse files
CopilotHnogared
authored andcommitted
feat: Add GCP Firestore health check implementation with tests
Co-authored-by: Hnogared <133124217+Hnogared@users.noreply.github.com>
1 parent e16e8d6 commit 225dc0b

File tree

16 files changed

+530
-5
lines changed

16 files changed

+530
-5
lines changed

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
<PackageVersion Include="DuckDB.NET.Data" Version="1.3.2" />
4242
<PackageVersion Include="Elastic.Clients.Elasticsearch" Version="9.1.8" />
4343
<PackageVersion Include="FirebirdSql.Data.FirebirdClient" Version="10.3.3" />
44+
<PackageVersion Include="Google.Cloud.Firestore" Version="3.10.0" />
4445
<PackageVersion Include="Keycloak.Net.Core" Version="1.0.35" />
4546
<PackageVersion Include="Microsoft.ApplicationInsights" Version="2.23.0" />
4647
<PackageVersion Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.23.0" />

GitVersion.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
mode: ManualDeployment
2-
major-version-bump-message: "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\\([\\w\\s-,/\\\\]*\\))?(!:|:.*\\n\\n((.+\\n)+\\n)?BREAKING CHANGE:\\s.+)"
3-
minor-version-bump-message: "^(feat)(\\([\\w\\s-,/\\\\]*\\))?:"
4-
patch-version-bump-message: "^(build|chore|ci|docs|fix|perf|refactor|revert|style|test)(\\([\\w\\s-,/\\\\]*\\))?:"
5-
workflow: TrunkBased/preview1
1+
mode: ContinuousDelivery
2+
branches: {}
3+
ignore:
4+
sha: []
5+
merge-message-formats: {}

HealthChecks.slnx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
<Project Path="src/NetEvolve.HealthChecks.DuckDB/NetEvolve.HealthChecks.DuckDB.csproj" />
4848
<Project Path="src/NetEvolve.HealthChecks.Elasticsearch/NetEvolve.HealthChecks.Elasticsearch.csproj" />
4949
<Project Path="src/NetEvolve.HealthChecks.Firebird/NetEvolve.HealthChecks.Firebird.csproj" />
50+
<Project Path="src/NetEvolve.HealthChecks.GCP.Firestore/NetEvolve.HealthChecks.GCP.Firestore.csproj" />
5051
<Project Path="src/NetEvolve.HealthChecks.Http/NetEvolve.HealthChecks.Http.csproj" />
5152
<Project Path="src/NetEvolve.HealthChecks.Keycloak/NetEvolve.HealthChecks.Keycloak.csproj" />
5253
<Project Path="src/NetEvolve.HealthChecks.MongoDb/NetEvolve.HealthChecks.MongoDb.csproj" />
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
namespace NetEvolve.HealthChecks.GCP.Firestore;
2+
3+
using System;
4+
using System.Diagnostics.CodeAnalysis;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.Diagnostics.HealthChecks;
7+
using NetEvolve.Arguments;
8+
9+
/// <summary>
10+
/// Extension methods for registering Firestore health checks.
11+
/// </summary>
12+
public static class DependencyInjectionExtensions
13+
{
14+
private static readonly string[] _defaultTags = ["firestore", "gcp"];
15+
16+
/// <summary>
17+
/// Adds a health check for Google Cloud Firestore.
18+
/// </summary>
19+
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
20+
/// <param name="name">The name of the health check.</param>
21+
/// <param name="options">An optional action to configure the <see cref="FirestoreOptions"/>.</param>
22+
/// <param name="tags">An optional list of tags to associate with the health check.</param>
23+
/// <returns>The <see cref="IHealthChecksBuilder"/> so that additional calls can be chained.</returns>
24+
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/> or <paramref name="name"/> is <see langword="null"/>.</exception>
25+
public static IHealthChecksBuilder AddFirestore(
26+
[NotNull] this IHealthChecksBuilder builder,
27+
[NotNull] string name,
28+
Action<FirestoreOptions>? options = null,
29+
params string[] tags
30+
)
31+
{
32+
Argument.ThrowIfNull(builder);
33+
Argument.ThrowIfNullOrWhiteSpace(name);
34+
35+
if (options is not null)
36+
{
37+
_ = builder.Services.Configure(name, options);
38+
}
39+
40+
return builder
41+
.AddCheck<FirestoreHealthCheck>(
42+
name,
43+
failureStatus: HealthStatus.Unhealthy,
44+
tags: [.. _defaultTags, .. tags]
45+
)
46+
.ConfigureOptionsService<FirestoreOptions, FirestoreOptionsConfigure>();
47+
}
48+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
namespace NetEvolve.HealthChecks.GCP.Firestore;
2+
3+
using System;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using global::Google.Cloud.Firestore;
7+
using Microsoft.Extensions.DependencyInjection;
8+
using Microsoft.Extensions.Diagnostics.HealthChecks;
9+
using Microsoft.Extensions.Options;
10+
using NetEvolve.Extensions.Tasks;
11+
using NetEvolve.HealthChecks.Abstractions;
12+
13+
internal sealed class FirestoreHealthCheck : ConfigurableHealthCheckBase<FirestoreOptions>
14+
{
15+
private readonly IServiceProvider _serviceProvider;
16+
17+
public FirestoreHealthCheck(IOptionsMonitor<FirestoreOptions> optionsMonitor, IServiceProvider serviceProvider)
18+
: base(optionsMonitor) => _serviceProvider = serviceProvider;
19+
20+
protected override async ValueTask<HealthCheckResult> ExecuteHealthCheckAsync(
21+
string name,
22+
HealthStatus failureStatus,
23+
FirestoreOptions options,
24+
CancellationToken cancellationToken
25+
)
26+
{
27+
var client = string.IsNullOrWhiteSpace(options.KeyedService)
28+
? _serviceProvider.GetRequiredService<FirestoreDb>()
29+
: _serviceProvider.GetRequiredKeyedService<FirestoreDb>(options.KeyedService);
30+
31+
var (isValid, _) = await client
32+
.ListRootCollectionsAsync()
33+
.GetAsyncEnumerator(cancellationToken)
34+
.MoveNextAsync()
35+
.AsTask()
36+
.WithTimeoutAsync(options.Timeout, cancellationToken)
37+
.ConfigureAwait(false);
38+
39+
return HealthCheckState(isValid, name);
40+
}
41+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
namespace NetEvolve.HealthChecks.GCP.Firestore;
2+
3+
/// <summary>
4+
/// Options for <see cref="FirestoreHealthCheck"/>
5+
/// </summary>
6+
public sealed record FirestoreOptions
7+
{
8+
/// <summary>
9+
/// Gets or sets the timeout in milliseconds to use when executing tasks against the Firestore database.
10+
/// </summary>
11+
/// <value>
12+
/// The timeout in milliseconds. Default value is 100 milliseconds.
13+
/// </value>
14+
public int Timeout { get; set; } = 100;
15+
16+
/// <summary>
17+
/// Gets or sets the keyed service name for retrieving the <see cref="Google.Cloud.Firestore.FirestoreDb"/> instance.
18+
/// </summary>
19+
/// <value>
20+
/// The keyed service name, or <c>null</c> if using the default service registration.
21+
/// </value>
22+
public string? KeyedService { get; set; }
23+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
namespace NetEvolve.HealthChecks.GCP.Firestore;
2+
3+
using Microsoft.Extensions.Configuration;
4+
using Microsoft.Extensions.Options;
5+
6+
internal sealed class FirestoreOptionsConfigure
7+
: IConfigureNamedOptions<FirestoreOptions>,
8+
IPostConfigureOptions<FirestoreOptions>
9+
{
10+
private readonly IConfiguration _configuration;
11+
12+
public FirestoreOptionsConfigure(IConfiguration configuration) => _configuration = configuration;
13+
14+
public void Configure(string? name, FirestoreOptions options)
15+
{
16+
ArgumentNullException.ThrowIfNull(name);
17+
18+
_configuration.Bind($"HealthChecks:GCP:Firestore:{name}", options);
19+
}
20+
21+
public void Configure(FirestoreOptions options) => Configure(Options.DefaultName, options);
22+
23+
public void PostConfigure(string? name, FirestoreOptions options)
24+
{
25+
if (options.Timeout < -1)
26+
{
27+
options.Timeout = -1;
28+
}
29+
}
30+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFrameworks>$(_ProjectTargetFrameworks)</TargetFrameworks>
4+
<Description>Contains HealthChecks for Google Cloud Platform Firestore, based on the nuget package `Google.Cloud.Firestore`.</Description>
5+
<PackageTags>$(PackageTags);gcp;google;firestore</PackageTags>
6+
</PropertyGroup>
7+
<ItemGroup>
8+
<PackageReference Include="Google.Cloud.Firestore" />
9+
<PackageReference Include="NetEvolve.Extensions.Tasks" />
10+
</ItemGroup>
11+
<ItemGroup>
12+
<ProjectReference Include="..\NetEvolve.HealthChecks.Abstractions\NetEvolve.HealthChecks.Abstractions.csproj" />
13+
</ItemGroup>
14+
</Project>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# NetEvolve.HealthChecks.GCP.Firestore
2+
3+
[![NuGet](https://img.shields.io/nuget/v/NetEvolve.HealthChecks.GCP.Firestore?logo=nuget)](https://www.nuget.org/packages/NetEvolve.HealthChecks.GCP.Firestore/)
4+
[![NuGet](https://img.shields.io/nuget/dt/NetEvolve.HealthChecks.GCP.Firestore?logo=nuget)](https://www.nuget.org/packages/NetEvolve.HealthChecks.GCP.Firestore/)
5+
6+
This package provides a health check for Google Cloud Platform Firestore, based on the [Google.Cloud.Firestore](https://www.nuget.org/packages/Google.Cloud.Firestore/) package. The main purpose is to check if the Firestore database is available and accessible.
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.GCP.Firestore
14+
```
15+
16+
## Health Check - Firestore Liveness
17+
The health check is a liveness check. It checks if the Firestore database is available and accessible.
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.GCP.Firestore;
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 `firestore` and `gcp` 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 Firestore instances to check.
35+
```csharp
36+
var builder = services.AddHealthChecks();
37+
38+
builder.AddFirestore("<name>");
39+
```
40+
41+
The configuration looks like this:
42+
```json
43+
{
44+
..., // other configuration
45+
"HealthChecks": {
46+
"GCP": {
47+
"Firestore": {
48+
"<name>": {
49+
"Timeout": "<timeout>" // optional, default is 100 milliseconds
50+
}
51+
}
52+
}
53+
}
54+
}
55+
```
56+
57+
### Variant 2: Builder based
58+
The second approach is to use the builder based approach. This approach is recommended if you only have one Firestore instance to check or dynamic programmatic values.
59+
```csharp
60+
var builder = services.AddHealthChecks();
61+
62+
builder.AddFirestore("<name>", options =>
63+
{
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.AddFirestore("<name>", options => ..., "firestore", "gcp");
74+
```

tests/NetEvolve.HealthChecks.Tests.Architecture/HealthCheckArchitecture.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ private static Architecture LoadArchitecture()
4444
typeof(DuckDB.DuckDBHealthCheck).Assembly,
4545
typeof(Elasticsearch.ElasticsearchHealthCheck).Assembly,
4646
typeof(Firebird.FirebirdHealthCheck).Assembly,
47+
typeof(GCP.Firestore.FirestoreHealthCheck).Assembly,
4748
typeof(Http.HttpHealthCheck).Assembly,
4849
typeof(Keycloak.KeycloakHealthCheck).Assembly,
4950
typeof(MongoDb.MongoDbHealthCheck).Assembly,

0 commit comments

Comments
 (0)