From b88f9c20d181de713565b054ca85a2c2a8a104a2 Mon Sep 17 00:00:00 2001 From: Odonno Date: Mon, 11 Aug 2025 12:10:58 +0200 Subject: [PATCH 1/6] fix(surrealdb): improve surrealdb healthchecks --- .../SurrealDbBuilderExtensions.cs | 87 ++++++++++--------- .../SurrealDbHealthCheck.cs | 4 +- 2 files changed, 50 insertions(+), 41 deletions(-) diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs index 711bc1c1e..7daface0a 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs @@ -81,7 +81,7 @@ public static IResourceBuilder AddSurrealServer( : SurrealDbContainerImageTags.Tag; var surrealServer = new SurrealDbServerResource(name, userName?.Resource, passwordParameter); - + return builder.AddResource(surrealServer) .WithEndpoint(port: port, targetPort: SurrealDbPort, name: SurrealDbServerResource.PrimaryEndpointName) .WithImage(SurrealDbContainerImageTags.Image, imageTag) @@ -106,33 +106,44 @@ public static IResourceBuilder AddSurrealServer( throw new DistributedApplicationException($"ResourceReadyEvent was published for the '{surrealServer.Name}' resource but the connection string was null."); } - var options = new SurrealDbOptionsBuilder().FromConnectionString(connectionString).Build(); - await using var surrealClient = new SurrealDbClient(options); + await EnsuresNsDbCreated(builder, connectionString, surrealServer, @event.Services, ct); + }); + } + + private static async Task EnsuresNsDbCreated( + IDistributedApplicationBuilder builder, + string connectionString, + SurrealDbServerResource surrealServer, + IServiceProvider services, + CancellationToken ct + ) + { + var options = new SurrealDbOptionsBuilder().FromConnectionString(connectionString).Build(); + await using var surrealClient = new SurrealDbClient(options); - foreach (var nsResourceName in surrealServer.Namespaces.Keys) - { - if (builder.Resources.FirstOrDefault(n => - string.Equals(n.Name, nsResourceName, StringComparison.OrdinalIgnoreCase)) is - SurrealDbNamespaceResource surrealDbNamespace) - { - await CreateNamespaceAsync(surrealClient, surrealDbNamespace, @event.Services, ct) - .ConfigureAwait(false); + foreach (var nsResourceName in surrealServer.Namespaces.Keys) + { + if (builder.Resources.FirstOrDefault(n => + string.Equals(n.Name, nsResourceName, StringComparison.OrdinalIgnoreCase)) is + SurrealDbNamespaceResource surrealDbNamespace) + { + await CreateNamespaceAsync(surrealClient, surrealDbNamespace, services, ct) + .ConfigureAwait(false); - await surrealClient.Use(surrealDbNamespace.NamespaceName, null!, ct).ConfigureAwait(false); + await surrealClient.Use(surrealDbNamespace.NamespaceName, null!, ct).ConfigureAwait(false); - foreach (var dbResourceName in surrealDbNamespace.Databases.Keys) - { - if (builder.Resources.FirstOrDefault(n => - string.Equals(n.Name, dbResourceName, StringComparison.OrdinalIgnoreCase)) is - SurrealDbDatabaseResource surrealDbDatabase) - { - await CreateDatabaseAsync(surrealClient, surrealDbDatabase, @event.Services, ct) - .ConfigureAwait(false); - } - } - } - } - }); + foreach (var dbResourceName in surrealDbNamespace.Databases.Keys) + { + if (builder.Resources.FirstOrDefault(n => + string.Equals(n.Name, dbResourceName, StringComparison.OrdinalIgnoreCase)) is + SurrealDbDatabaseResource surrealDbDatabase) + { + await CreateDatabaseAsync(surrealClient, surrealDbDatabase, services, ct) + .ConfigureAwait(false); + } + } + } + } } /// @@ -234,24 +245,10 @@ public static IResourceBuilder AddDatabase( SurrealDbClient? surrealDbClient = null; - builder.ApplicationBuilder.Eventing.Subscribe(surrealServerDatabase, async (@event, ct) => - { - var connectionString = await surrealServerDatabase.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false); - if (connectionString is null) - { - throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{surrealServerDatabase}' resource but the connection string was null."); - } - - var options = new SurrealDbOptionsBuilder().FromConnectionString(connectionString).Build(); - surrealDbClient = new SurrealDbClient(options); - }); - string namespaceName = builder.Resource.Name; string serverName = builder.Resource.Parent.Name; string healthCheckKey = $"{serverName}_{namespaceName}_{name}_check"; - // TODO : Bug to be fixed - //builder.ApplicationBuilder.Services.AddHealthChecks().AddSurreal(_ => surrealDbClient!, healthCheckKey); builder.ApplicationBuilder.Services.AddHealthChecks().Add(new HealthCheckRegistration( name: healthCheckKey, _ => new SurrealDbHealthCheck(surrealDbClient!), @@ -261,7 +258,17 @@ public static IResourceBuilder AddDatabase( ); return builder.ApplicationBuilder.AddResource(surrealServerDatabase) - .WithHealthCheck(healthCheckKey); + .WithHealthCheck(healthCheckKey) + .OnConnectionStringAvailable(async (_, _, ct) => { + var connectionString = await surrealServerDatabase.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false); + if (connectionString is null) + { + throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{surrealServerDatabase}' resource but the connection string was null."); + } + + var options = new SurrealDbOptionsBuilder().FromConnectionString(connectionString).Build(); + surrealDbClient = new SurrealDbClient(options); + }); } /// diff --git a/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbHealthCheck.cs b/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbHealthCheck.cs index 963d50f05..fc5480dd9 100644 --- a/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbHealthCheck.cs +++ b/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbHealthCheck.cs @@ -22,7 +22,9 @@ public async Task CheckHealthAsync(HealthCheckContext context try { bool isHealthy = await _surrealdbClient.Health(cancellationToken).ConfigureAwait(false); - + var response = await _surrealdbClient.RawQuery("RETURN 1", cancellationToken: cancellationToken).ConfigureAwait(false); + response.EnsureAllOks(); + return isHealthy ? HealthCheckResult.Healthy() : new HealthCheckResult(context.Registration.FailureStatus); From 55c521df6bb87bf7960472a28f01a8b12157c2e0 Mon Sep 17 00:00:00 2001 From: Odonno Date: Mon, 11 Aug 2025 21:58:02 +0200 Subject: [PATCH 2/6] fix(surrealdb): escape password value --- .../SurrealDbServerResource.cs | 2 +- .../AddSurrealServerTests.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbServerResource.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbServerResource.cs index a351fb0aa..98b5da2c8 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbServerResource.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbServerResource.cs @@ -54,7 +54,7 @@ UserNameParameter is not null ? private ReferenceExpression ConnectionString => ReferenceExpression.Create( - $"Server={SchemeUri}://{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}/rpc;User={UserNameReference};Password={PasswordParameter}"); + $"Server={SchemeUri}://{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}/rpc;User={UserNameReference};Password='{PasswordParameter}'"); /// /// Gets the connection string expression for the SurrealDB instance. diff --git a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AddSurrealServerTests.cs b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AddSurrealServerTests.cs index 61a202aae..4ec182b27 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AddSurrealServerTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AddSurrealServerTests.cs @@ -91,8 +91,8 @@ public async Task SurrealServerCreatesConnectionString() var connectionStringResource = Assert.Single(appModel.Resources.OfType()); var connectionString = await connectionStringResource.GetConnectionStringAsync(default); - Assert.Equal("Server=ws://localhost:8000/rpc;User=root;Password=p@ssw0rd1", connectionString); - Assert.Equal("Server=ws://{surreal.bindings.tcp.host}:{surreal.bindings.tcp.port}/rpc;User=root;Password={pass.value}", connectionStringResource.ConnectionStringExpression.ValueExpression); + Assert.Equal("Server=ws://localhost:8000/rpc;User=root;Password='p@ssw0rd1'", connectionString); + Assert.Equal("Server=ws://{surreal.bindings.tcp.host}:{surreal.bindings.tcp.port}/rpc;User=root;Password='{pass.value}'", connectionStringResource.ConnectionStringExpression.ValueExpression); } [Fact] @@ -116,7 +116,7 @@ public async Task SurrealServerDatabaseCreatesConnectionString() var connectionStringResource = (IResourceWithConnectionString)surrealResource; var connectionString = await connectionStringResource.GetConnectionStringAsync(); - Assert.Equal("Server=ws://localhost:8000/rpc;User=root;Password=p@ssw0rd1;Namespace=myns;Database=mydb", connectionString); + Assert.Equal("Server=ws://localhost:8000/rpc;User=root;Password='p@ssw0rd1';Namespace=myns;Database=mydb", connectionString); Assert.Equal("{ns.connectionString};Database=mydb", connectionStringResource.ConnectionStringExpression.ValueExpression); } From 1c97df8648ab64a695687daa266f34f2277b6ceb Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Thu, 21 Aug 2025 16:05:42 +1000 Subject: [PATCH 3/6] Adding some logging statements in the health check for diagnostics --- .../SurrealDbBuilderExtensions.cs | 73 ++++++++++--------- .../AspireSurrealDbExtensions.cs | 6 +- .../SurrealDbHealthCheck.cs | 14 +++- 3 files changed, 52 insertions(+), 41 deletions(-) diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs index 7daface0a..55bf52259 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs @@ -72,14 +72,14 @@ public static IResourceBuilder AddSurrealServer( { args.Add("--strict"); } - + // The password must be at least 8 characters long and contain characters from three of the following four sets: Uppercase letters, Lowercase letters, Base 10 digits, and Symbols var passwordParameter = password?.Resource ?? ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder, $"{name}-password", minLower: 1, minUpper: 1, minNumeric: 1); string imageTag = builder.ExecutionContext.IsRunMode ? $"{SurrealDbContainerImageTags.Tag}-dev" : SurrealDbContainerImageTags.Tag; - + var surrealServer = new SurrealDbServerResource(name, userName?.Resource, passwordParameter); return builder.AddResource(surrealServer) @@ -99,19 +99,19 @@ public static IResourceBuilder AddSurrealServer( { return; } - + var connectionString = await surrealServer.GetConnectionStringAsync(ct).ConfigureAwait(false); if (connectionString is null) { throw new DistributedApplicationException($"ResourceReadyEvent was published for the '{surrealServer.Name}' resource but the connection string was null."); } - + await EnsuresNsDbCreated(builder, connectionString, surrealServer, @event.Services, ct); }); } private static async Task EnsuresNsDbCreated( - IDistributedApplicationBuilder builder, + IDistributedApplicationBuilder builder, string connectionString, SurrealDbServerResource surrealServer, IServiceProvider services, @@ -120,7 +120,7 @@ CancellationToken ct { var options = new SurrealDbOptionsBuilder().FromConnectionString(connectionString).Build(); await using var surrealClient = new SurrealDbClient(options); - + foreach (var nsResourceName in surrealServer.Namespaces.Keys) { if (builder.Resources.FirstOrDefault(n => @@ -129,9 +129,9 @@ CancellationToken ct { await CreateNamespaceAsync(surrealClient, surrealDbNamespace, services, ct) .ConfigureAwait(false); - + await surrealClient.Use(surrealDbNamespace.NamespaceName, null!, ct).ConfigureAwait(false); - + foreach (var dbResourceName in surrealDbNamespace.Databases.Keys) { if (builder.Resources.FirstOrDefault(n => @@ -185,7 +185,7 @@ public static IResourceBuilder AddNamespace( var surrealServerNamespace = new SurrealDbNamespaceResource(name, namespaceName, builder.Resource); return builder.ApplicationBuilder.AddResource(surrealServerNamespace); } - + /// /// Defines the SQL script used to create the namespace. /// @@ -251,15 +251,16 @@ public static IResourceBuilder AddDatabase( string healthCheckKey = $"{serverName}_{namespaceName}_{name}_check"; builder.ApplicationBuilder.Services.AddHealthChecks().Add(new HealthCheckRegistration( name: healthCheckKey, - _ => new SurrealDbHealthCheck(surrealDbClient!), + sp => new SurrealDbHealthCheck(surrealDbClient!, sp.GetRequiredService>()), failureStatus: null, tags: null ) ); - + return builder.ApplicationBuilder.AddResource(surrealServerDatabase) .WithHealthCheck(healthCheckKey) - .OnConnectionStringAvailable(async (_, _, ct) => { + .OnConnectionStringAvailable(async (_, _, ct) => + { var connectionString = await surrealServerDatabase.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false); if (connectionString is null) { @@ -267,10 +268,10 @@ public static IResourceBuilder AddDatabase( } var options = new SurrealDbOptionsBuilder().FromConnectionString(connectionString).Build(); - surrealDbClient = new SurrealDbClient(options); + surrealDbClient = new SurrealDbClient(options); }); } - + /// /// Defines the SQL script used to create the database. /// @@ -354,7 +355,7 @@ public static IResourceBuilder WithDataBindMount(this I return builder.WithBindMount(source, "/data"); } - + /// /// Copies init files into a SurrealDB container resource. /// @@ -374,10 +375,10 @@ public static IResourceBuilder WithInitFiles(this IReso { throw new DistributedApplicationException($"Unable to determine the file name for '{source}'."); } - + string fileName = Path.GetFileName(importFullPath); string initFilePath = $"{initPath}/{fileName}"; - + return builder .WithContainerFiles(initPath, importFullPath) .WithEnvironment(context => @@ -420,16 +421,16 @@ public static IResourceBuilder WithSurrealist( .WithHttpEndpoint(targetPort: 8080, name: "http") .WithRelationship(builder.Resource, "Surrealist") .ExcludeFromManifest(); - + surrealistContainerBuilder.WithContainerFiles( destinationPath: "/usr/share/nginx/html", callback: async (_, cancellationToken) => { - var surrealDbServerInstances = + var surrealDbServerInstances = builder.ApplicationBuilder.Resources.OfType().ToList(); - var surrealDbNamespaceResources = + var surrealDbNamespaceResources = builder.ApplicationBuilder.Resources.OfType().ToList(); - var surrealDbDatabaseResources = + var surrealDbDatabaseResources = builder.ApplicationBuilder.Resources.OfType().ToList(); return [ @@ -438,8 +439,8 @@ public static IResourceBuilder WithSurrealist( Name = "instance.json", Contents = await WriteSurrealistInstanceJson( surrealDbServerInstances, - surrealDbNamespaceResources, - surrealDbDatabaseResources, + surrealDbNamespaceResources, + surrealDbDatabaseResources, cancellationToken ).ConfigureAwait(false), }, @@ -450,7 +451,7 @@ public static IResourceBuilder WithSurrealist( return builder; } - + private static async Task WriteSurrealistInstanceJson( IList surrealDbServerInstances, IList surrealDbNamespaceResources, @@ -529,16 +530,16 @@ CancellationToken cancellationToken writer.WriteEndArray(); writer.WriteEndObject(); - + await writer.FlushAsync(cancellationToken); - + return Encoding.UTF8.GetString(stream.ToArray()); } - + private static async Task CreateNamespaceAsync( - SurrealDbClient surrealClient, - SurrealDbNamespaceResource namespaceResource, - IServiceProvider serviceProvider, + SurrealDbClient surrealClient, + SurrealDbNamespaceResource namespaceResource, + IServiceProvider serviceProvider, CancellationToken cancellationToken ) { @@ -550,7 +551,7 @@ CancellationToken cancellationToken try { var response = await surrealClient.RawQuery( - scriptAnnotation?.Script ?? $"DEFINE NAMESPACE IF NOT EXISTS `{namespaceResource.NamespaceName}`;", + scriptAnnotation?.Script ?? $"DEFINE NAMESPACE IF NOT EXISTS `{namespaceResource.NamespaceName}`;", cancellationToken: cancellationToken ).ConfigureAwait(false); @@ -563,11 +564,11 @@ CancellationToken cancellationToken logger.LogError(e, "Failed to create namespace '{NamespaceName}'", namespaceResource.NamespaceName); } } - + private static async Task CreateDatabaseAsync( - SurrealDbClient surrealClient, - SurrealDbDatabaseResource databaseResource, - IServiceProvider serviceProvider, + SurrealDbClient surrealClient, + SurrealDbDatabaseResource databaseResource, + IServiceProvider serviceProvider, CancellationToken cancellationToken ) { @@ -579,7 +580,7 @@ CancellationToken cancellationToken try { var response = await surrealClient.RawQuery( - scriptAnnotation?.Script ?? $"DEFINE DATABASE IF NOT EXISTS `{databaseResource.DatabaseName}`;", + scriptAnnotation?.Script ?? $"DEFINE DATABASE IF NOT EXISTS `{databaseResource.DatabaseName}`;", cancellationToken: cancellationToken ).ConfigureAwait(false); diff --git a/src/CommunityToolkit.Aspire.SurrealDb/AspireSurrealDbExtensions.cs b/src/CommunityToolkit.Aspire.SurrealDb/AspireSurrealDbExtensions.cs index c1821ae25..7a830fbbf 100644 --- a/src/CommunityToolkit.Aspire.SurrealDb/AspireSurrealDbExtensions.cs +++ b/src/CommunityToolkit.Aspire.SurrealDb/AspireSurrealDbExtensions.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; using SurrealDb.Net; namespace Microsoft.Extensions.Hosting; @@ -16,7 +17,7 @@ namespace Microsoft.Extensions.Hosting; public static class AspireSurrealDbExtensions { private const string DefaultConfigSectionName = "Aspire:Surreal:Client"; - + /// /// Registers in the services provided by the . /// @@ -99,7 +100,8 @@ private static void AddSurrealClient( healthCheckName, sp => new SurrealDbHealthCheck(serviceKey is null ? sp.GetRequiredService() : - sp.GetRequiredKeyedService(serviceKey)), + sp.GetRequiredKeyedService(serviceKey), + sp.GetRequiredService>()), failureStatus: null, tags: null, timeout: settings.HealthCheckTimeout > 0 ? TimeSpan.FromMilliseconds(settings.HealthCheckTimeout.Value) : null diff --git a/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbHealthCheck.cs b/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbHealthCheck.cs index fc5480dd9..440a17323 100644 --- a/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbHealthCheck.cs +++ b/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbHealthCheck.cs @@ -3,34 +3,42 @@ using SurrealDb.Net; using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; namespace CommunityToolkit.Aspire.SurrealDb; internal sealed class SurrealDbHealthCheck : IHealthCheck { private readonly ISurrealDbClient _surrealdbClient; + private readonly ILogger _logger; - public SurrealDbHealthCheck(ISurrealDbClient surrealdbClient) + public SurrealDbHealthCheck(ISurrealDbClient surrealdbClient, ILogger logger) { ArgumentNullException.ThrowIfNull(surrealdbClient, nameof(surrealdbClient)); _surrealdbClient = surrealdbClient; + _logger = logger; } /// public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { + bool isHealthy = false; try { - bool isHealthy = await _surrealdbClient.Health(cancellationToken).ConfigureAwait(false); + isHealthy = await _surrealdbClient.Health(cancellationToken).ConfigureAwait(false); var response = await _surrealdbClient.RawQuery("RETURN 1", cancellationToken: cancellationToken).ConfigureAwait(false); response.EnsureAllOks(); - + + _logger.LogInformation("SurrealDB health check passed. Response: {Response}", response); + _logger.LogInformation("SurrealDB health check outcome: {Outcome}", isHealthy ? "Healthy" : "Unhealthy"); + return isHealthy ? HealthCheckResult.Healthy() : new HealthCheckResult(context.Registration.FailureStatus); } catch (Exception ex) { + _logger.LogError(ex, "SurrealDB health check raised an exception. Health check had previously reported: {Outcome}.", isHealthy ? "Healthy" : "Unhealthy"); return new HealthCheckResult(context.Registration.FailureStatus, exception: ex); } } From 6e0d4eb28ecdd789d3a4a3a8283ee1ea981dd126 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Thu, 21 Aug 2025 06:18:06 +0000 Subject: [PATCH 4/6] Bit more diag messaging --- src/CommunityToolkit.Aspire.SurrealDb/SurrealDbHealthCheck.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbHealthCheck.cs b/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbHealthCheck.cs index 440a17323..57daca54e 100644 --- a/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbHealthCheck.cs +++ b/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbHealthCheck.cs @@ -38,7 +38,7 @@ public async Task CheckHealthAsync(HealthCheckContext context } catch (Exception ex) { - _logger.LogError(ex, "SurrealDB health check raised an exception. Health check had previously reported: {Outcome}.", isHealthy ? "Healthy" : "Unhealthy"); + _logger.LogError(ex, "SurrealDB health check raised an exception. Health check had previously reported: {Outcome}. CancellationToken status: {CancellationTokenStatus}", isHealthy ? "Healthy" : "Unhealthy", cancellationToken.IsCancellationRequested ? "Canceled" : "Active"); return new HealthCheckResult(context.Registration.FailureStatus, exception: ex); } } From 78ef4862e46268cadd65bee7190072e1cc2c9c3c Mon Sep 17 00:00:00 2001 From: Odonno Date: Fri, 22 Aug 2025 18:12:40 +0200 Subject: [PATCH 5/6] fix: new client for each healthcheck call --- .../SurrealDbBuilderExtensions.cs | 7 +++---- .../AspireSurrealDbExtensions.cs | 5 ++--- .../SurrealDbHealthCheck.cs | 15 +++++++++------ 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs index 55bf52259..6bc375ec1 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs @@ -243,7 +243,7 @@ public static IResourceBuilder AddDatabase( builder.Resource.AddDatabase(name, databaseName); var surrealServerDatabase = new SurrealDbDatabaseResource(name, databaseName, builder.Resource); - SurrealDbClient? surrealDbClient = null; + SurrealDbOptions? surrealDbOptions = null; string namespaceName = builder.Resource.Name; string serverName = builder.Resource.Parent.Name; @@ -251,7 +251,7 @@ public static IResourceBuilder AddDatabase( string healthCheckKey = $"{serverName}_{namespaceName}_{name}_check"; builder.ApplicationBuilder.Services.AddHealthChecks().Add(new HealthCheckRegistration( name: healthCheckKey, - sp => new SurrealDbHealthCheck(surrealDbClient!, sp.GetRequiredService>()), + sp => new SurrealDbHealthCheck(surrealDbOptions!, sp.GetRequiredService>()), failureStatus: null, tags: null ) @@ -267,8 +267,7 @@ public static IResourceBuilder AddDatabase( throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{surrealServerDatabase}' resource but the connection string was null."); } - var options = new SurrealDbOptionsBuilder().FromConnectionString(connectionString).Build(); - surrealDbClient = new SurrealDbClient(options); + surrealDbOptions = new SurrealDbOptionsBuilder().FromConnectionString(connectionString).Build(); }); } diff --git a/src/CommunityToolkit.Aspire.SurrealDb/AspireSurrealDbExtensions.cs b/src/CommunityToolkit.Aspire.SurrealDb/AspireSurrealDbExtensions.cs index 7a830fbbf..bb740499d 100644 --- a/src/CommunityToolkit.Aspire.SurrealDb/AspireSurrealDbExtensions.cs +++ b/src/CommunityToolkit.Aspire.SurrealDb/AspireSurrealDbExtensions.cs @@ -98,9 +98,8 @@ private static void AddSurrealClient( builder.TryAddHealthCheck(new HealthCheckRegistration( healthCheckName, - sp => new SurrealDbHealthCheck(serviceKey is null ? - sp.GetRequiredService() : - sp.GetRequiredKeyedService(serviceKey), + sp => new SurrealDbHealthCheck( + settings.Options, sp.GetRequiredService>()), failureStatus: null, tags: null, diff --git a/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbHealthCheck.cs b/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbHealthCheck.cs index 57daca54e..e3ea730d2 100644 --- a/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbHealthCheck.cs +++ b/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbHealthCheck.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.Extensions.DependencyInjection; using SurrealDb.Net; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Logging; @@ -9,13 +10,12 @@ namespace CommunityToolkit.Aspire.SurrealDb; internal sealed class SurrealDbHealthCheck : IHealthCheck { - private readonly ISurrealDbClient _surrealdbClient; + private readonly SurrealDbOptions _options; private readonly ILogger _logger; - public SurrealDbHealthCheck(ISurrealDbClient surrealdbClient, ILogger logger) + public SurrealDbHealthCheck(SurrealDbOptions options, ILogger logger) { - ArgumentNullException.ThrowIfNull(surrealdbClient, nameof(surrealdbClient)); - _surrealdbClient = surrealdbClient; + _options = options; _logger = logger; } @@ -23,10 +23,13 @@ public SurrealDbHealthCheck(ISurrealDbClient surrealdbClient, ILogger CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { bool isHealthy = false; + try { - isHealthy = await _surrealdbClient.Health(cancellationToken).ConfigureAwait(false); - var response = await _surrealdbClient.RawQuery("RETURN 1", cancellationToken: cancellationToken).ConfigureAwait(false); + await using var surrealdbClient = new SurrealDbClient(_options); + + isHealthy = await surrealdbClient.Health(cancellationToken).ConfigureAwait(false); + var response = await surrealdbClient.RawQuery("RETURN 1", cancellationToken: cancellationToken).ConfigureAwait(false); response.EnsureAllOks(); _logger.LogInformation("SurrealDB health check passed. Response: {Response}", response); From 9be7e8c0d6000fe8ef15a44e1bfd201a71cfb8ee Mon Sep 17 00:00:00 2001 From: Odonno Date: Fri, 22 Aug 2025 18:55:44 +0200 Subject: [PATCH 6/6] fix: wait until ns created --- .../SurrealDbBuilderExtensions.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs index 6bc375ec1..d58be809e 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs @@ -130,7 +130,19 @@ CancellationToken ct await CreateNamespaceAsync(surrealClient, surrealDbNamespace, services, ct) .ConfigureAwait(false); - await surrealClient.Use(surrealDbNamespace.NamespaceName, null!, ct).ConfigureAwait(false); + // 💡 Wait until the Namespace is really created?! + while (!ct.IsCancellationRequested) + { + try + { + await surrealClient.Use(surrealDbNamespace.NamespaceName, null!, ct).ConfigureAwait(false); + break; + } + catch + { + await Task.Delay(200, ct).ConfigureAwait(false); + } + } foreach (var dbResourceName in surrealDbNamespace.Databases.Keys) {