From 45f3008785f8d48b450a28b5f0c21f82df50068f Mon Sep 17 00:00:00 2001 From: Odonno Date: Thu, 14 Nov 2024 15:57:08 +0100 Subject: [PATCH 01/30] feat: add surrealdb hosting/client integration --- CommunityToolkit.Aspire.slnx | 2 + Directory.Packages.props | 1 + ...ityToolkit.Aspire.Hosting.SurrealDb.csproj | 17 ++ .../PublicAPI.Shipped.txt | 1 + .../PublicAPI.Unshipped.txt | 26 ++ .../README.md | 35 +++ .../SurrealDbBuilderExtensions.cs | 259 ++++++++++++++++++ .../SurrealDbContainerImageTags.cs | 11 + .../SurrealDbDatabaseResource.cs | 45 +++ .../SurrealDbNamespaceResource.cs | 57 ++++ .../SurrealDbServerResource.cs | 101 +++++++ .../AspireSurrealDbExtensions.cs | 109 ++++++++ .../CommunityToolkit.Aspire.SurrealDb.csproj | 20 ++ .../PublicAPI.Shipped.txt | 1 + .../PublicAPI.Unshipped.txt | 14 + .../README.md | 121 ++++++++ .../SurrealDbClientSettings.cs | 39 +++ .../SurrealDbHealthCheck.cs | 35 +++ 18 files changed, 894 insertions(+) create mode 100644 src/CommunityToolkit.Aspire.Hosting.SurrealDb/CommunityToolkit.Aspire.Hosting.SurrealDb.csproj create mode 100644 src/CommunityToolkit.Aspire.Hosting.SurrealDb/PublicAPI.Shipped.txt create mode 100644 src/CommunityToolkit.Aspire.Hosting.SurrealDb/PublicAPI.Unshipped.txt create mode 100644 src/CommunityToolkit.Aspire.Hosting.SurrealDb/README.md create mode 100644 src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs create mode 100644 src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbContainerImageTags.cs create mode 100644 src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbDatabaseResource.cs create mode 100644 src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbNamespaceResource.cs create mode 100644 src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbServerResource.cs create mode 100644 src/CommunityToolkit.Aspire.SurrealDb/AspireSurrealDbExtensions.cs create mode 100644 src/CommunityToolkit.Aspire.SurrealDb/CommunityToolkit.Aspire.SurrealDb.csproj create mode 100644 src/CommunityToolkit.Aspire.SurrealDb/PublicAPI.Shipped.txt create mode 100644 src/CommunityToolkit.Aspire.SurrealDb/PublicAPI.Unshipped.txt create mode 100644 src/CommunityToolkit.Aspire.SurrealDb/README.md create mode 100644 src/CommunityToolkit.Aspire.SurrealDb/SurrealDbClientSettings.cs create mode 100644 src/CommunityToolkit.Aspire.SurrealDb/SurrealDbHealthCheck.cs diff --git a/CommunityToolkit.Aspire.slnx b/CommunityToolkit.Aspire.slnx index fdffae1d..983fdf4a 100644 --- a/CommunityToolkit.Aspire.slnx +++ b/CommunityToolkit.Aspire.slnx @@ -172,12 +172,14 @@ + + diff --git a/Directory.Packages.props b/Directory.Packages.props index 95f5bab4..088f36b6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -41,6 +41,7 @@ + diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/CommunityToolkit.Aspire.Hosting.SurrealDb.csproj b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/CommunityToolkit.Aspire.Hosting.SurrealDb.csproj new file mode 100644 index 00000000..bbaaf2e1 --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/CommunityToolkit.Aspire.Hosting.SurrealDb.csproj @@ -0,0 +1,17 @@ + + + + hosting surrealdb + SurrealDB support for .NET Aspire. + + + + + + + + + + + + diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/PublicAPI.Shipped.txt b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/PublicAPI.Shipped.txt new file mode 100644 index 00000000..91b0e1a4 --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/PublicAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/PublicAPI.Unshipped.txt b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/PublicAPI.Unshipped.txt new file mode 100644 index 00000000..16eb67b7 --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/PublicAPI.Unshipped.txt @@ -0,0 +1,26 @@ +#nullable enable +Aspire.Hosting.ApplicationModel.SurrealDbDatabaseResource +Aspire.Hosting.ApplicationModel.SurrealDbDatabaseResource.ConnectionStringExpression.get -> Aspire.Hosting.ApplicationModel.ReferenceExpression! +Aspire.Hosting.ApplicationModel.SurrealDbDatabaseResource.DatabaseName.get -> string! +Aspire.Hosting.ApplicationModel.SurrealDbDatabaseResource.Parent.get -> Aspire.Hosting.ApplicationModel.SurrealDbNamespaceResource! +Aspire.Hosting.ApplicationModel.SurrealDbDatabaseResource.SurrealDbDatabaseResource(string! name, string! databaseName, Aspire.Hosting.ApplicationModel.SurrealDbNamespaceResource! parent) -> void +Aspire.Hosting.ApplicationModel.SurrealDbNamespaceResource +Aspire.Hosting.ApplicationModel.SurrealDbNamespaceResource.ConnectionStringExpression.get -> Aspire.Hosting.ApplicationModel.ReferenceExpression! +Aspire.Hosting.ApplicationModel.SurrealDbNamespaceResource.Databases.get -> System.Collections.Generic.IReadOnlyDictionary! +Aspire.Hosting.ApplicationModel.SurrealDbNamespaceResource.NamespaceName.get -> string! +Aspire.Hosting.ApplicationModel.SurrealDbNamespaceResource.Parent.get -> Aspire.Hosting.ApplicationModel.SurrealDbServerResource! +Aspire.Hosting.ApplicationModel.SurrealDbNamespaceResource.SurrealDbNamespaceResource(string! name, string! namespaceName, Aspire.Hosting.ApplicationModel.SurrealDbServerResource! parent) -> void +Aspire.Hosting.ApplicationModel.SurrealDbServerResource +Aspire.Hosting.ApplicationModel.SurrealDbServerResource.ConnectionStringExpression.get -> Aspire.Hosting.ApplicationModel.ReferenceExpression! +Aspire.Hosting.ApplicationModel.SurrealDbServerResource.GetConnectionStringAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +Aspire.Hosting.ApplicationModel.SurrealDbServerResource.Namespaces.get -> System.Collections.Generic.IReadOnlyDictionary! +Aspire.Hosting.ApplicationModel.SurrealDbServerResource.PasswordParameter.get -> Aspire.Hosting.ApplicationModel.ParameterResource! +Aspire.Hosting.ApplicationModel.SurrealDbServerResource.PrimaryEndpoint.get -> Aspire.Hosting.ApplicationModel.EndpointReference! +Aspire.Hosting.ApplicationModel.SurrealDbServerResource.SurrealDbServerResource(string! name, Aspire.Hosting.ApplicationModel.ParameterResource? userName, Aspire.Hosting.ApplicationModel.ParameterResource! password) -> void +Aspire.Hosting.ApplicationModel.SurrealDbServerResource.UserNameParameter.get -> Aspire.Hosting.ApplicationModel.ParameterResource? +Aspire.Hosting.SurrealDbBuilderExtensions +static Aspire.Hosting.SurrealDbBuilderExtensions.AddDatabase(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string! name, string? databaseName = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! +static Aspire.Hosting.SurrealDbBuilderExtensions.AddNamespace(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string! name, string? namespaceName = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! +static Aspire.Hosting.SurrealDbBuilderExtensions.AddSurrealServer(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name, Aspire.Hosting.ApplicationModel.IResourceBuilder? userName = null, Aspire.Hosting.ApplicationModel.IResourceBuilder? password = null, int? port = null, bool strictMode = false) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! +static Aspire.Hosting.SurrealDbBuilderExtensions.WithDataBindMount(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string! source) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! +static Aspire.Hosting.SurrealDbBuilderExtensions.WithDataVolume(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string? name = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/README.md b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/README.md new file mode 100644 index 00000000..8eb99b95 --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/README.md @@ -0,0 +1,35 @@ +# CommunityToolkit.Aspire.Hosting.SurrealDb library + +Provides extension methods and resource definitions for the .NET Aspire AppHost to support running [SurrealDB](https://surrealdb.com/) containers. + +## Getting started + +### Install the package + +In your AppHost project, install the .NET Aspire SurrealDB Hosting library with [NuGet](https://www.nuget.org): + +```dotnetcli +dotnet add package CommunityToolkit.Aspire.Hosting.SurrealDb +``` + +## Usage example + +Then, in the _Program.cs_ file of `AppHost`, add a SurrealDB resource and consume the connection using the following methods: + +```csharp +var db = builder.AddSurrealServer("surreal") + .AddNamespace("ns") + .AddDatabase("db"); + +var myService = builder.AddProject() + .WithReference(db); +``` + +## Additional Information + +https://learn.microsoft.com/dotnet/aspire/community-toolkit/hosting-surrealdb + +## Feedback & contributing + +https://github.com/CommunityToolkit/Aspire + diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs new file mode 100644 index 00000000..35ff3e90 --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs @@ -0,0 +1,259 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Utils; +using CommunityToolkit.Aspire.SurrealDb; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using SurrealDb.Net; + +namespace Aspire.Hosting; + +/// +/// Provides extension methods for adding SurrealDB resources to the application model. +/// +public static class SurrealDbBuilderExtensions +{ + private const string UserEnvVarName = "SURREAL_USER"; + private const string PasswordEnvVarName = "SURREAL_PASS"; + + /// + /// Adds a SurrealDB resource to the application model. A container is used for local development. + /// + /// The . + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// The parameter used to provide the administrator username for the SurrealDB resource. + /// The parameter used to provide the administrator password for the SurrealDB resource. If a random password will be generated. + /// The host port for the SurrealDB instance. + /// Whether strict mode is enabled on the server. + /// A reference to the . + /// + /// + /// Add a SurrealDB container to the application model and reference it in a .NET project. + /// + /// var builder = DistributedApplication.CreateBuilder(args); + /// + /// var db = builder.AddSurrealServer("surreal") + /// .AddNamespace("ns") + /// .AddDatabase("db"); + /// var api = builder.AddProject<Projects.Api>("api") + /// .WithReference(db); + /// + /// builder.Build().Run(); + /// + /// + /// + public static IResourceBuilder AddSurrealServer( + this IDistributedApplicationBuilder builder, + [ResourceName] string name, + IResourceBuilder? userName = null, + IResourceBuilder? password = null, + int? port = null, + bool strictMode = false + ) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentException.ThrowIfNullOrEmpty(name); + + var args = new List + { + "start" + }; + if (strictMode) + { + 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); + + var surrealServer = new SurrealDbServerResource(name, userName?.Resource, passwordParameter); + return builder.AddResource(surrealServer) + .WithEndpoint(port: port, targetPort: 8000, name: SurrealDbServerResource.PrimaryEndpointName) + .WithImage(SurrealDbContainerImageTags.Image, SurrealDbContainerImageTags.Tag) + .WithImageRegistry(SurrealDbContainerImageTags.Registry) + .WithEnvironment(context => + { + context.EnvironmentVariables[UserEnvVarName] = surrealServer.UserNameReference; + context.EnvironmentVariables[PasswordEnvVarName] = surrealServer.PasswordParameter; + }) + .WithEntrypoint("/surreal") + .WithArgs([.. args]); + } + + /// + /// Adds a SurrealDB namespace to the application model. This is a child resource of a . + /// + /// The SurrealDB resource builders. + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// The name of the namespace. If not provided, this defaults to the same value as . + /// A reference to the . + /// + /// + /// Add a SurrealDB container to the application model and reference it in a .NET project. + /// + /// var builder = DistributedApplication.CreateBuilder(args); + /// + /// var db = builder.AddSurrealServer("surreal") + /// .AddNamespace("ns") + /// .AddDatabase("db"); + /// var api = builder.AddProject<Projects.Api>("api") + /// .WithReference(db); + /// + /// builder.Build().Run(); + /// + /// + /// + public static IResourceBuilder AddNamespace( + this IResourceBuilder builder, + [ResourceName] string name, + string? namespaceName = null + ) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentException.ThrowIfNullOrEmpty(name); + + // Use the resource name as the namespace name if it's not provided + namespaceName ??= name; + + builder.Resource.AddNamespace(name, namespaceName); + var surrealServerNamespace = new SurrealDbNamespaceResource(name, namespaceName, builder.Resource); + return builder.ApplicationBuilder.AddResource(surrealServerNamespace); + } + + /// + /// Adds a SurrealDB database to the application model. This is a child resource of a . + /// + /// The SurrealDB resource builders. + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// The name of the database. If not provided, this defaults to the same value as . + /// A reference to the . + /// + /// + /// Add a SurrealDB container to the application model and reference it in a .NET project. + /// + /// var builder = DistributedApplication.CreateBuilder(args); + /// + /// var db = builder.AddSurrealServer("surreal") + /// .AddNamespace("ns") + /// .AddDatabase("db"); + /// var api = builder.AddProject<Projects.Api>("api") + /// .WithReference(db); + /// + /// builder.Build().Run(); + /// + /// + /// + public static IResourceBuilder AddDatabase( + this IResourceBuilder builder, + [ResourceName] string name, + string? databaseName = null + ) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentException.ThrowIfNullOrEmpty(name); + + // Use the resource name as the database name if it's not provided + databaseName ??= name; + + builder.Resource.AddDatabase(name, databaseName); + var surrealServerDatabase = new SurrealDbDatabaseResource(name, databaseName, builder.Resource); + + SurrealDbClient? surrealDbClient = null; + + builder.ApplicationBuilder.Eventing.Subscribe(surrealServerDatabase, async (@event, ct) => + { + var connectionString = await surrealServerDatabase.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false); + if (connectionString == null) + { + throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{surrealServerDatabase}' resource but the connection string was null."); + } + + surrealDbClient = new SurrealDbClient(connectionString); + }); + + string namespaceName = builder.Resource.Name; + string serverName = builder.Resource.Parent.Name; + + string healthCheckKey = $"{serverName}_{namespaceName}_{name}_check"; + builder.ApplicationBuilder.Services.AddHealthChecks() + .Add(new HealthCheckRegistration( + healthCheckKey, + sp => new SurrealDbHealthCheck(surrealDbClient!), + failureStatus: default, + tags: default, + timeout: default) + ); + + return builder.ApplicationBuilder.AddResource(surrealServerDatabase) + .WithHealthCheck(healthCheckKey); + } + + /// + /// Adds a named volume for the data folder to a SurrealDB resource. + /// + /// The resource builder. + /// The name of the volume. Defaults to an auto-generated name based on the application and resource names. + /// The . + /// + /// + /// Add an Meilisearch container to the application model and reference it in a .NET project. + /// Additionally, in this example a data volume is added to the container + /// to allow data to be persisted across container restarts. + /// + /// var builder = DistributedApplication.CreateBuilder(args); + /// + /// var db = builder.AddSurrealServer("surreal") + /// .WithDataVolume() + /// .AddNamespace("ns") + /// .AddDatabase("db"); + /// var api = builder.AddProject<Projects.Api>("api") + /// .WithReference(db); + /// + /// builder.Build().Run(); + /// + /// + /// + public static IResourceBuilder WithDataVolume(this IResourceBuilder builder, string? name = null) + { + ArgumentNullException.ThrowIfNull(builder); + +#pragma warning disable CTASPIRE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + return builder.WithVolume(name ?? VolumeNameGenerator.CreateVolumeName(builder, "data"), "/var/opt/surreal"); +#pragma warning restore CTASPIRE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + } + + /// + /// Adds a bind mount for the data folder to a SurrealDB resource. + /// + /// The resource builder. + /// The source directory on the host to mount into the container. + /// The . + /// + /// + /// Add a SurrealDB container to the application model and reference it in a .NET project. + /// Additionally, in this example a bind mount is added to the container + /// to allow data to be persisted across container restarts. + /// + /// var builder = DistributedApplication.CreateBuilder(args); + /// + /// var db = builder.AddSurrealServer("surreal") + /// .WithDataBindMount("./data/surreal/data") + /// .AddNamespace("ns") + /// .AddDatabase("db"); + /// var api = builder.AddProject<Projects.Api>("api") + /// .WithReference(db); + /// + /// builder.Build().Run(); + /// + /// + /// + public static IResourceBuilder WithDataBindMount(this IResourceBuilder builder, string source) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentException.ThrowIfNullOrEmpty(source); + + return builder.WithBindMount(source, "/var/opt/surreal"); + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbContainerImageTags.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbContainerImageTags.cs new file mode 100644 index 00000000..efb03611 --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbContainerImageTags.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting; + +internal sealed class SurrealDbContainerImageTags +{ + public const string Registry = "docker.io"; + public const string Image = "surrealdb/surrealdb"; + public const string Tag = "v2.0.4"; +} \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbDatabaseResource.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbDatabaseResource.cs new file mode 100644 index 00000000..247a714b --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbDatabaseResource.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +/// +/// A resource that represents a SurrealDB database that is a child of a SurrealDB namespace resource. +/// +public class SurrealDbDatabaseResource : Resource, IResourceWithParent, IResourceWithConnectionString +{ + /// + /// Gets the parent SurrealDB namespace resource. + /// + public SurrealDbNamespaceResource Parent { get; } + + /// + /// Gets the connection string expression for the SurrealDB database. + /// + public ReferenceExpression ConnectionStringExpression => + ReferenceExpression.Create($"{Parent};Database={DatabaseName}"); + + /// + /// Gets the database name. + /// + public string DatabaseName { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the resource. + /// The database name. + /// The parent SurrealDB namespace resource. + public SurrealDbDatabaseResource( + [ResourceName] string name, + string databaseName, + SurrealDbNamespaceResource parent + ) : base(name) + { + ArgumentException.ThrowIfNullOrEmpty(databaseName); + ArgumentNullException.ThrowIfNull(parent); + + DatabaseName = databaseName; + Parent = parent; + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbNamespaceResource.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbNamespaceResource.cs new file mode 100644 index 00000000..22305347 --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbNamespaceResource.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +/// +/// A resource that represents a SurrealDB namespace that is a child of a SurrealDB container resource. +/// +public class SurrealDbNamespaceResource : Resource, IResourceWithParent, IResourceWithConnectionString +{ + /// + /// Gets the parent SurrealDB container resource. + /// + public SurrealDbServerResource Parent { get; } + + /// + /// Gets the connection string expression for the SurrealDB database. + /// + public ReferenceExpression ConnectionStringExpression => + ReferenceExpression.Create($"{Parent};Namespace={NamespaceName}"); + + /// + /// Gets the namespace name. + /// + public string NamespaceName { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the resource. + /// The namespace name. + /// The parent SurrealDB server resource. + public SurrealDbNamespaceResource( + [ResourceName] string name, + string namespaceName, + SurrealDbServerResource parent + ) : base(name) + { + ArgumentException.ThrowIfNullOrEmpty(namespaceName); + ArgumentNullException.ThrowIfNull(parent); + + NamespaceName = namespaceName; + Parent = parent; + } + + private readonly Dictionary _databases = new(StringComparer.Ordinal); + + /// + /// A dictionary where the key is the resource name and the value is the database name. + /// + public IReadOnlyDictionary Databases => _databases; + + internal void AddDatabase(string name, string databaseName) + { + _databases.TryAdd(name, databaseName); + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbServerResource.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbServerResource.cs new file mode 100644 index 00000000..eae7f334 --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbServerResource.cs @@ -0,0 +1,101 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +/// +/// A resource that represents a SurrealDB container. +/// +public class SurrealDbServerResource : ContainerResource, IResourceWithConnectionString +{ + internal const string PrimaryEndpointName = "tcp"; + + private const string DefaultUserName = "root"; + private const string SchemeUri = "ws"; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the resource. + /// A parameter that contains the SurrealDB username. + /// A parameter that contains the SurrealDB password. + public SurrealDbServerResource( + [ResourceName] string name, + ParameterResource? userName, + ParameterResource password + ) : base(name) + { + ArgumentNullException.ThrowIfNull(password); + + PrimaryEndpoint = new(this, PrimaryEndpointName); + UserNameParameter = userName; + PasswordParameter = password; + } + + /// + /// Gets the primary endpoint for the SurrealDB instance. + /// + public EndpointReference PrimaryEndpoint { get; } + + /// + /// Gets the parameter that contains the SurrealDB username. + /// + public ParameterResource? UserNameParameter { get; } + + internal ReferenceExpression UserNameReference => + UserNameParameter is not null ? + ReferenceExpression.Create($"{UserNameParameter}") : + ReferenceExpression.Create($"{DefaultUserName}"); + + /// + /// Gets the parameter that contains the SurrealDB password. + /// + public ParameterResource PasswordParameter { get; } + + private ReferenceExpression ConnectionString => + ReferenceExpression.Create( + $"Server={SchemeUri}://{PrimaryEndpoint.Property(EndpointProperty.IPV4Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}/rpc;User={UserNameReference};Password={PasswordParameter}"); + + /// + /// Gets the connection string expression for the SurrealDB instance. + /// + public ReferenceExpression ConnectionStringExpression + { + get + { + if (this.TryGetLastAnnotation(out var connectionStringAnnotation)) + { + return connectionStringAnnotation.Resource.ConnectionStringExpression; + } + + return ConnectionString; + } + } + + /// + /// Gets the connection string for the SurrealDB instance. + /// + /// A to observe while waiting for the task to complete. + /// A connection string for the SurrealDB instance in the form "Server=scheme://host:port;User=username;Password=password". + public ValueTask GetConnectionStringAsync(CancellationToken cancellationToken = default) + { + if (this.TryGetLastAnnotation(out var connectionStringAnnotation)) + { + return connectionStringAnnotation.Resource.GetConnectionStringAsync(cancellationToken); + } + + return ConnectionString.GetValueAsync(cancellationToken); + } + + private readonly Dictionary _namespaces = new(StringComparer.Ordinal); + + /// + /// A dictionary where the key is the resource name and the value is the namespace name. + /// + public IReadOnlyDictionary Namespaces => _namespaces; + + internal void AddNamespace(string name, string namespaceName) + { + _namespaces.TryAdd(name, namespaceName); + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.SurrealDb/AspireSurrealDbExtensions.cs b/src/CommunityToolkit.Aspire.SurrealDb/AspireSurrealDbExtensions.cs new file mode 100644 index 00000000..b91515cd --- /dev/null +++ b/src/CommunityToolkit.Aspire.SurrealDb/AspireSurrealDbExtensions.cs @@ -0,0 +1,109 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire; +using CommunityToolkit.Aspire.SurrealDb; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using SurrealDb.Net; + +namespace Microsoft.Extensions.Hosting; + +/// +/// Provides extension methods for registering SurrealDB-related services in an . +/// +public static class AspireSurrealDbExtensions +{ + private const string DefaultConfigSectionName = "Aspire:Surreal:Client"; + + /// + /// Registers in the services provided by the . + /// + /// The to read config from and add services to. + /// The connection name to use to find a connection string. + /// An optional method that can be used for customizing the . It's invoked after the settings are read from the configuration. + /// Reads the configuration from "Aspire:Surreal:Client" section. + /// If required ConnectionString is not provided in configuration section + public static void AddSurrealClient( + this IHostApplicationBuilder builder, + string connectionName, + Action? configureSettings = null + ) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNullOrEmpty(connectionName); + AddSurrealClient(builder, DefaultConfigSectionName, configureSettings, connectionName, serviceKey: null); + } + + /// + /// Registers as a keyed service for the given in the services provided by the . + /// + /// The to read config from and add services to. + /// The connection name to use to find a connection string. + /// An optional method that can be used for customizing the . It's invoked after the settings are read from the configuration. + /// Reads the configuration from "Aspire:Surreal:Client" section. + /// If required ConnectionString is not provided in configuration section + public static void AddKeyedSurrealClient( + this IHostApplicationBuilder builder, + string name, + Action? configureSettings = null + ) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNullOrEmpty(name); + AddSurrealClient(builder, $"{DefaultConfigSectionName}:{name}", configureSettings, connectionName: name, serviceKey: name); + } + + private static void AddSurrealClient( + this IHostApplicationBuilder builder, + string configurationSectionName, + Action? configureSettings, + string connectionName, + string? serviceKey + ) + { + ArgumentNullException.ThrowIfNull(builder); + + var settings = new SurrealDbClientSettings(); + builder.Configuration.GetSection(configurationSectionName).Bind(settings); + + if (builder.Configuration.GetConnectionString(connectionName) is string connectionString) + { + settings.Options = SurrealDbOptions.Create().FromConnectionString(connectionString).Build(); + } + + configureSettings?.Invoke(settings); + + if (settings.Options is null) + { + throw new NullReferenceException("SurrealDB configured options cannot be null."); + } + + if (serviceKey is null) + { + builder.Services.AddSurreal(settings.Options, settings.Lifetime); + } + else + { + // TODO : Implement AddKeyedSurreal + //builder.Services.AddKeyedSurreal(serviceKey, settings.Options, settings.Lifetime); + } + + if (!settings.DisableHealthChecks) + { + string healthCheckName = serviceKey is null ? "surrealdb" : $"surrealdb_{connectionName}"; + + builder.TryAddHealthCheck(new HealthCheckRegistration( + healthCheckName, + sp => new SurrealDbHealthCheck(serviceKey is null ? + sp.GetRequiredService() : + sp.GetRequiredKeyedService(serviceKey)), + failureStatus: null, + tags: null, + timeout: settings.HealthCheckTimeout > 0 ? TimeSpan.FromMilliseconds(settings.HealthCheckTimeout.Value) : null + ) + ); + } + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.SurrealDb/CommunityToolkit.Aspire.SurrealDb.csproj b/src/CommunityToolkit.Aspire.SurrealDb/CommunityToolkit.Aspire.SurrealDb.csproj new file mode 100644 index 00000000..441df04b --- /dev/null +++ b/src/CommunityToolkit.Aspire.SurrealDb/CommunityToolkit.Aspire.SurrealDb.csproj @@ -0,0 +1,20 @@ + + + + surrealdb client + A SurrealDB client that integrates with Aspire, including health checks, logging, and telemetry. + + + + + + + + + + + + + + + diff --git a/src/CommunityToolkit.Aspire.SurrealDb/PublicAPI.Shipped.txt b/src/CommunityToolkit.Aspire.SurrealDb/PublicAPI.Shipped.txt new file mode 100644 index 00000000..91b0e1a4 --- /dev/null +++ b/src/CommunityToolkit.Aspire.SurrealDb/PublicAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.SurrealDb/PublicAPI.Unshipped.txt b/src/CommunityToolkit.Aspire.SurrealDb/PublicAPI.Unshipped.txt new file mode 100644 index 00000000..e76c8bc0 --- /dev/null +++ b/src/CommunityToolkit.Aspire.SurrealDb/PublicAPI.Unshipped.txt @@ -0,0 +1,14 @@ +#nullable enable +CommunityToolkit.Aspire.SurrealDb.SurrealDbClientSettings +CommunityToolkit.Aspire.SurrealDb.SurrealDbClientSettings.DisableHealthChecks.get -> bool +CommunityToolkit.Aspire.SurrealDb.SurrealDbClientSettings.DisableHealthChecks.set -> void +CommunityToolkit.Aspire.SurrealDb.SurrealDbClientSettings.HealthCheckTimeout.get -> int? +CommunityToolkit.Aspire.SurrealDb.SurrealDbClientSettings.HealthCheckTimeout.set -> void +CommunityToolkit.Aspire.SurrealDb.SurrealDbClientSettings.Lifetime.get -> Microsoft.Extensions.DependencyInjection.ServiceLifetime +CommunityToolkit.Aspire.SurrealDb.SurrealDbClientSettings.Lifetime.set -> void +CommunityToolkit.Aspire.SurrealDb.SurrealDbClientSettings.Options.get -> Microsoft.Extensions.DependencyInjection.SurrealDbOptions? +CommunityToolkit.Aspire.SurrealDb.SurrealDbClientSettings.Options.set -> void +CommunityToolkit.Aspire.SurrealDb.SurrealDbClientSettings.SurrealDbClientSettings() -> void +Microsoft.Extensions.Hosting.AspireSurrealDbExtensions +static Microsoft.Extensions.Hosting.AspireSurrealDbExtensions.AddKeyedSurrealClient(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! name, System.Action? configureSettings = null) -> void +static Microsoft.Extensions.Hosting.AspireSurrealDbExtensions.AddSurrealClient(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! connectionName, System.Action? configureSettings = null) -> void diff --git a/src/CommunityToolkit.Aspire.SurrealDb/README.md b/src/CommunityToolkit.Aspire.SurrealDb/README.md new file mode 100644 index 00000000..ad6c687b --- /dev/null +++ b/src/CommunityToolkit.Aspire.SurrealDb/README.md @@ -0,0 +1,121 @@ +# CommunityToolkit.Aspire.SurrealDb + +Registers a [SurrealDbClient](https://github.com/surrealdb/surrealdb.net) in the DI container for connecting to a SurrealDB instance. + +## Getting started + +### Prerequisites + +- SurrealDB cluster. + +### Install the package + +Install the .NET Aspire SurrealDB Client library with [NuGet](https://www.nuget.org): + +```dotnetcli +dotnet add package CommunityToolkit.Aspire.SurrealDb +``` + +## Usage example + +In the _Program.cs_ file of your project, call the `AddSurrealClient` extension method to register a `SurrealDbClient` for use via the dependency injection container. The method takes a connection name parameter. + +```csharp +builder.AddSurrealClient("surreal"); +``` + +## Configuration + +The .NET Aspire SurrealDB Client integration provides multiple options to configure the server connection based on the requirements and conventions of your project. + +### Use a connection string + +When using a connection string from the `ConnectionStrings` configuration section, you can provide the name of the connection string when calling `builder.AddSurrealClient()`: + +```csharp +builder.AddSurrealClient("surreal"); +``` + +And then the connection string will be retrieved from the `ConnectionStrings` configuration section: + +```json +{ + "ConnectionStrings": { + "surreal": "Server=ws://127.0.0.1:8000/rpc;Namespace=test;Database=test;Username=root;Password=root" + } +} +``` + +### Use configuration providers + +The .NET Aspire SurrealDB Client integration supports [Microsoft.Extensions.Configuration](https://learn.microsoft.com/dotnet/api/microsoft.extensions.configuration). It loads the `SurrealDbClientSettings` from configuration by using the `Aspire:Surreal:Client` key. Example `appsettings.json` that configures some of the options: + +```json +{ + "Aspire": { + "Surreal": { + "Client": { + "Options": { + "Endpoint": "ws://127.0.0.1:8000/rpc", + "Namespace": "test", + "Database": "test", + "Username": "root", + "Password": "root" + } + } + } + } +} +``` + +### Use inline delegates + +Also you can pass the `Action configureSettings` delegate to set up some or all the options inline, for example to set the API key from code: + +```csharp +builder.AddSurrealDbClient("surreal", settings => settings.Options.Endpoint = "ws://localhost:8000/rpc"); +``` + +## AppHost extensions + +In your AppHost project, install the `CommunityToolkit.Aspire.Hosting.SurrealDb` library with [NuGet](https://www.nuget.org): + +```dotnetcli +dotnet add package CommunityToolkit.Aspire.Hosting.SurrealDb +``` + +Then, in the _Program.cs_ file of `AppHost`, register a SurrealDB cluster and consume the connection using the following methods: + +```csharp +var db = builder.AddSurrealServer("surreal") + .AddNamespace("ns") + .AddDatabase("db"); + +var myService = builder.AddProject() + .WithReference(db); +``` + +The `WithReference` method configures a connection in the `MyService` project named `db`. In the _Program.cs_ file of `MyService`, the SurrealDB connection can be consumed using: + +```csharp +builder.AddSurrealClient("db"); +``` + +Then, in your service, inject `SurrealDbClient` and use it to interact with the SurrealDB instance: + +```csharp +public class MyService(SurrealDbClient client) +{ + // ... +} +``` + +## Additional documentation + +- https://github.com/surrealdb/surrealdb.net +- https://learn.microsoft.com/dotnet/aspire/community-toolkit/hosting-surrealdb + +## Feedback & contributing + +https://github.com/CommunityToolkit/Aspire + diff --git a/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbClientSettings.cs b/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbClientSettings.cs new file mode 100644 index 00000000..02c07719 --- /dev/null +++ b/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbClientSettings.cs @@ -0,0 +1,39 @@ +// 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; + +namespace CommunityToolkit.Aspire.SurrealDb; + +/// +/// Provides the client configuration settings for connecting to a SurrealDB server using . +/// +public sealed class SurrealDbClientSettings +{ + /// + /// The defined options used to connect to the SurrealDB server. + /// + public SurrealDbOptions? Options { get; set; } + + /// + /// Gets or sets the Service lifetime to register services under. + /// + /// + /// The default value is . + /// + public ServiceLifetime Lifetime { get; set; } = ServiceLifetime.Singleton; + + /// + /// Gets or sets a boolean value that indicates whether the SurrealDB health check is disabled or not. + /// + /// + /// The default value is . + /// + public bool DisableHealthChecks { get; set; } + + /// + /// Gets or sets a integer value that indicates the SurrealDB health check timeout in milliseconds. + /// + public int? HealthCheckTimeout { get; set; } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbHealthCheck.cs b/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbHealthCheck.cs new file mode 100644 index 00000000..963d50f0 --- /dev/null +++ b/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbHealthCheck.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using SurrealDb.Net; +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace CommunityToolkit.Aspire.SurrealDb; + +internal sealed class SurrealDbHealthCheck : IHealthCheck +{ + private readonly ISurrealDbClient _surrealdbClient; + + public SurrealDbHealthCheck(ISurrealDbClient surrealdbClient) + { + ArgumentNullException.ThrowIfNull(surrealdbClient, nameof(surrealdbClient)); + _surrealdbClient = surrealdbClient; + } + + /// + public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) + { + try + { + bool isHealthy = await _surrealdbClient.Health(cancellationToken).ConfigureAwait(false); + + return isHealthy + ? HealthCheckResult.Healthy() + : new HealthCheckResult(context.Registration.FailureStatus); + } + catch (Exception ex) + { + return new HealthCheckResult(context.Registration.FailureStatus, exception: ex); + } + } +} From 9ce637d8ae1a81b398207764f8eab137e297f766 Mon Sep 17 00:00:00 2001 From: Odonno Date: Thu, 14 Nov 2024 16:05:50 +0100 Subject: [PATCH 02/30] docs: update readme --- README.md | 87 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 49 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 877d88d3..ef1e5aab 100644 --- a/README.md +++ b/README.md @@ -12,44 +12,46 @@ All features are contributed by you, our amazing .NET community, and maintained This repository contains the source code for the .NET Aspire Community Toolkit, a collection of community created Integrations and extensions for [.NET Aspire](https://aka.ms/dotnet/aspire). -| Package | Description | -|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| - **Learn More**: [`Hosting.Azure.StaticWebApps`][swa-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Azure.StaticWebApps][swa-shields]][swa-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Azure.StaticWebApps][swa-shields-preview]][swa-nuget-preview] | A hosting integration for the [Azure Static Web Apps emulator](https://learn.microsoft.com/azure/static-web-apps/static-web-apps-cli-overview) (Note: this does not support deployment of a project to Azure Static Web Apps). | -| - **Learn More**: [`Hosting.Golang`][golang-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Golang][golang-shields]][golang-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Golang][golang-shields-preview]][golang-nuget-preview] | A hosting integration Golang apps. | -| - **Learn More**: [`Hosting.Java`][java-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Java][java-shields]][java-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Java][java-shields-preview]][java-nuget-preview] | An integration for running Java code in .NET Aspire either using the local JDK or using a container. | -| - **Learn More**: [`Hosting.NodeJS.Extensions`][nodejs-ext-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.NodeJS.Extensions][nodejs-ext-shields]][nodejs-ext-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.NodeJS.Extensions][nodejs-ext-shields-preview]][nodejs-ext-nuget-preview] | An integration that contains some additional extensions for running Node.js applications | -| - **Learn More**: [`Hosting.Ollama`][ollama-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Ollama][ollama-shields]][ollama-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Ollama][ollama-shields-preview]][ollama-nuget-preview] | An Aspire hosting integration leveraging the [Ollama](https://ollama.com) container with support for downloading a model on startup. | -| - **Learn More**: [`OllamaSharp`][ollama-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.OllamaSharp][ollamasharp-shields]][ollamasharp-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.OllamaSharp][ollamasharp-shields-preview]][ollamasharp-nuget-preview] | An Aspire client integration for the [OllamaSharp](https://github.com/awaescher/OllamaSharp) package. | -| - **Learn More**: [`Hosting.Meilisearch`][meilisearch-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Meilisearch][meilisearch-shields]][meilisearch-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Meilisearch][meilisearch-shields-preview]][meilisearch-nuget-preview] | An Aspire hosting integration leveraging the [Meilisearch](https://meilisearch.com) container. | -| - **Learn More**: [`Meilisearch`][meilisearch-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Meilisearch][meilisearch-client-shields]][meilisearch-client-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Meilisearch][meilisearch-client-shields-preview]][meilisearch-client-nuget-preview] | An Aspire client integration for the [Meilisearch](https://github.com/meilisearch/meilisearch-dotnet) package. | -| - **Learn More**: [`Hosting.Azure.DataApiBuilder`][dab-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Azure.DataApiBuilder][dab-shields]][dab-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Azure.DataApiBuilder][dab-shields-preview]][dab-nuget-preview] | A hosting integration for the [Azure Data API builder](https://learn.microsoft.com/en-us/azure/data-api-builder/overview). | -| - **Learn More**: [`Hosting.Deno`][deno-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Deno][deno-shields]][deno-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Deno][deno-shields-preview]][deno-nuget-preview] | A hosting integration for the Deno apps. | -| - **Learn More**: [`Hosting.SqlDatabaseProjects`][sql-database-projects-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects][sql-database-projects-shields]][sql-database-projects-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects][sql-database-projects-shields-preview]][sql-database-projects-nuget-preview] | A hosting integration for the SQL Databases Projects. | -| - **Learn More**: [`Hosting.Rust`][rust-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Rust][rust-shields]][rust-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Rust][rust-shields-preview]][rust-nuget-preview] | A hosting integration for the Rust apps. | -| - **Learn More**: [`Hosting.Bun`][bun-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Bun][bun-shields]][bun-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Bun][bun-shields-preview]][bun-nuget-preview] | A hosting integration for the Bun apps. | -| - **Learn More**: [`Hosting.Python.Extensions`][python-ext-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Python.Extensions][python-ext-shields]][python-ext-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Python.Extensions][python-ext-shields-preview]][python-ext-nuget-preview] | An integration that contains some additional extensions for running python applications | -| - **Learn More**: [`Hosting.EventStore`][eventstore-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.EventStore][eventstore-shields]][eventstore-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.EventStore][eventstore-shields-preview]][eventstore-nuget-preview] | An Aspire hosting integration leveraging the [EventStore](https://eventstore.com) container. | -| - **Learn More**: [`EventStore`][eventstore-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.EventStore][eventstore-client-shields]][eventstore-client-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.EventStore][eventstore-client-shields-preview]][eventstore-client-nuget-preview] | An Aspire client integration for the [EventStore](https://github.com/EventStore/EventStore-Client-Dotnet) package. | -| - **Learn More**: [`Hosting.ActiveMQ`][activemq-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.ActiveMQ][activemq-shields]][activemq-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.ActiveMQ][activemq-shields-preview]][activemq-nuget-preview] | An Aspire hosting integration leveraging the [ActiveMq](https://activemq.apache.org) container. | -| - **Learn More**: [`Hosting.Sqlite`][sqlite-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Sqlite][sqlite-shields]][sqlite-hosting-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Sqlite][sqlite-shields-preview]][sqlite-hosting-nuget-preview] | An Aspire hosting integration to setup a SQLite database with optional SQLite Web as a dev UI. | -| - **Learn More**: [`Microsoft.Data.Sqlite`][sqlite-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Microsoft.Data.Sqlite][sqlite-shields]][sqlite-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Microsoft.Data.Sqlite][sqlite-shields-preview]][sqlite-nuget-preview] | An Aspire client integration for the Microsoft.Data.Sqlite NuGet package. | -| - **Learn More**: [`Microsoft.EntityFrameworkCore.Sqlite`][sqlite-ef-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Microsoft.EntityFrameworkCore.Sqlite][sqlite-ef-shields]][sqlite-ef-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Microsoft.EntityFrameworkCore.Sqlite][sqlite-ef-shields-preview]][sqlite-ef-nuget-preview] | An Aspire client integration for the Microsoft.EntityFrameworkCore.Sqlite NuGet package. | -| - **Learn More**: [`Hosting.Dapr`][dapr-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Dapr][dapr-shields]][dapr-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Dapr][dapr-shields-preview]][dapr-nuget-preview] | An Aspire hosting integration for Dapr. | -| - **Learn More**: [`Hosting.Azure.Dapr.Redis`][dapr-azureredis-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Azure.Dapr.Redis][dapr-azureredis-shields]][dapr-azureredis-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Azure.Dapr.Redis][dapr-azureredis-shields-preview]][dapr-azureredis-nuget-preview] | An extension for the Dapr hosting integration for using Dapr with Azure Redis cache. | -| - **Learn More**: [`Hosting.RavenDB`][ravendb-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.RavenDB][ravendb-shields]][ravendb-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.RavenDB][ravendb-shields-preview]][ravendb-nuget-preview] | An Aspire integration leveraging the [RavenDB](https://ravendb.net/) container. | -| - **Learn More**: [`RavenDB.Client`][ravendb-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.RavenDB.Client][ravendb-client-shields]][ravendb-client-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.RavenDB.Client][ravendb-client-shields-preview]][ravendb-client-nuget-preview] | An Aspire client integration for the [RavenDB.Client](https://www.nuget.org/packages/RavenDB.client) package. | -| - **Learn More**: [`Hosting.GoFeatureFlag`][go-feature-flag-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.GoFeatureFlag][go-feature-flag-shields]][go-feature-flag-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.GoFeatureFlag][go-feature-flag-shields-preview]][go-feature-flag-nuget-preview] | An Aspire hosting integration leveraging the [GoFeatureFlag](https://gofeatureflag.org/) container. | -| - **Learn More**: [`GoFeatureFlag`][go-feature-flag-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.GoFeatureFlag][go-feature-flag-client-shields]][go-feature-flag-client-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.GoFeatureFlag][go-feature-flag-client-shields-preview]][go-feature-flag-client-nuget-preview] | An Aspire client integration for the [GoFeatureFlag](https://github.com/open-feature/dotnet-sdk-contrib/tree/main/src/OpenFeature.Contrib.Providers.GOFeatureFlag) package. | -| - **Learn More**: [`Hosting.MongoDB.Extensions`][mongodb-ext-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.MongoDB.Extensions][mongodb-ext-shields]][mongodb-ext-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.MongoDB.Extensions][mongodb-ext-shields-preview]][mongodb-ext-nuget-preview] | An integration that contains some additional extensions for hosting MongoDB container. | -| - **Learn More**: [`Hosting.PostgreSQL.Extensions`][postgres-ext-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.PostgreSQL.Extensions][postgres-ext-shields]][postgres-ext-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.PostgreSQL.Extensions][postgres-ext-shields-preview]][postgres-ext-nuget-preview] | An integration that contains some additional extensions for hosting PostgreSQL container. | -| - **Learn More**: [`Hosting.Redis.Extensions`][redis-ext-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Redis.Extensions][redis-ext-shields]][redis-ext-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Redis.Extensions][redis-ext-shields-preview]][redis-ext-nuget-preview] | An integration that contains some additional extensions for hosting Redis container. | -| - **Learn More**: [`Hosting.SqlServer.Extensions`][sqlserver-ext-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.SqlServer.Extensions][sqlserver-ext-shields]][sqlserver-ext-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.SqlServer.Extensions][sqlserver-ext-shields-preview]][sqlserver-ext-nuget-preview] | An integration that contains some additional extensions for hosting SqlServer container. | -| - **Learn More**: [`Hosting.LavinMQ`][lavinmq-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.LavinMQ][lavinmq-shields]][lavinmq-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.LavinMQ][lavinmq-shields-preview]][lavinmq-nuget-preview] | An Aspire hosting integration for [LavinMQ](https://www.lavinmq.com). | -| - **Learn More**: [`Hosting.MailPit`][mailpit-ext-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.MailPit][mailpit-ext-shields]][mailpit-ext-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.MailPit][mailpit-ext-shields-preview]][mailpit-ext-nuget-preview] | An Aspire integration leveraging the [MailPit](https://mailpit.axllent.org/) container. | -| - **Learn More**: [`Hosting.k6`][k6-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.k6][k6-shields]][k6-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.k6][k6-shields-preview]][k6-nuget-preview] | An Aspire integration leveraging the [Grafana k6](https://k6.io/) container. | -| - **Learn More**: [`Hosting.MySql.Extensions`][mysql-ext-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.MySql.Extensions][mysql-ext-shields]][mysql-ext-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.MySql.Extensions][mysql-ext-shields-preview]][mysql-ext-nuget-preview] | An integration that contains some additional extensions for hosting MySql container. | -| - **Learn More**: [`Hosting.MinIO`][minio-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Minio][minio-hosting-shields]][minio-hosting-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Minio][minio-hosting-shields-preview]][minio-hosting-nuget-preview] | An Aspire hosting integration to setup a [MinIO S3](https://min.io/) storage. | -| - **Learn More**: [`MinIO.Client`][minio-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Minio.Client][minio-client-shields]][minio-client-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Client.Minio][minio-client-shields-preview]][minio-client-nuget-preview] | An Aspire client integration for the [MinIO](https://github.com/minio/minio-dotnet) package. | +| Package | Description | +| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| - **Learn More**: [`Hosting.Azure.StaticWebApps`][swa-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Azure.StaticWebApps][swa-shields]][swa-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Azure.StaticWebApps][swa-shields-preview]][swa-nuget-preview] | A hosting integration for the [Azure Static Web Apps emulator](https://learn.microsoft.com/azure/static-web-apps/static-web-apps-cli-overview) (Note: this does not support deployment of a project to Azure Static Web Apps). | +| - **Learn More**: [`Hosting.Golang`][golang-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Golang][golang-shields]][golang-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Golang][golang-shields-preview]][golang-nuget-preview] | A hosting integration Golang apps. | +| - **Learn More**: [`Hosting.Java`][java-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Java][java-shields]][java-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Java][java-shields-preview]][java-nuget-preview] | An integration for running Java code in .NET Aspire either using the local JDK or using a container. | +| - **Learn More**: [`Hosting.NodeJS.Extensions`][nodejs-ext-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.NodeJS.Extensions][nodejs-ext-shields]][nodejs-ext-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.NodeJS.Extensions][nodejs-ext-shields-preview]][nodejs-ext-nuget-preview] | An integration that contains some additional extensions for running Node.js applications | +| - **Learn More**: [`Hosting.Ollama`][ollama-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Ollama][ollama-shields]][ollama-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Ollama][ollama-shields-preview]][ollama-nuget-preview] | An Aspire hosting integration leveraging the [Ollama](https://ollama.com) container with support for downloading a model on startup. | +| - **Learn More**: [`OllamaSharp`][ollama-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.OllamaSharp][ollamasharp-shields]][ollamasharp-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.OllamaSharp][ollamasharp-shields-preview]][ollamasharp-nuget-preview] | An Aspire client integration for the [OllamaSharp](https://github.com/awaescher/OllamaSharp) package. | +| - **Learn More**: [`Hosting.Meilisearch`][meilisearch-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Meilisearch][meilisearch-shields]][meilisearch-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Meilisearch][meilisearch-shields-preview]][meilisearch-nuget-preview] | An Aspire hosting integration leveraging the [Meilisearch](https://meilisearch.com) container. | +| - **Learn More**: [`Meilisearch`][meilisearch-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Meilisearch][meilisearch-client-shields]][meilisearch-client-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Meilisearch][meilisearch-client-shields-preview]][meilisearch-client-nuget-preview] | An Aspire client integration for the [Meilisearch](https://github.com/meilisearch/meilisearch-dotnet) package. | +| - **Learn More**: [`Hosting.Azure.DataApiBuilder`][dab-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Azure.DataApiBuilder][dab-shields]][dab-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Azure.DataApiBuilder][dab-shields-preview]][dab-nuget-preview] | A hosting integration for the [Azure Data API builder](https://learn.microsoft.com/en-us/azure/data-api-builder/overview). | +| - **Learn More**: [`Hosting.Deno`][deno-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Deno][deno-shields]][deno-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Deno][deno-shields-preview]][deno-nuget-preview] | A hosting integration for the Deno apps. | +| - **Learn More**: [`Hosting.SqlDatabaseProjects`][sql-database-projects-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects][sql-database-projects-shields]][sql-database-projects-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects][sql-database-projects-shields-preview]][sql-database-projects-nuget-preview] | A hosting integration for the SQL Databases Projects. | +| - **Learn More**: [`Hosting.Rust`][rust-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Rust][rust-shields]][rust-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Rust][rust-shields-preview]][rust-nuget-preview] | A hosting integration for the Rust apps. | +| - **Learn More**: [`Hosting.Bun`][bun-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Bun][bun-shields]][bun-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Bun][bun-shields-preview]][bun-nuget-preview] | A hosting integration for the Bun apps. | +| - **Learn More**: [`Hosting.Python.Extensions`][python-ext-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Python.Extensions][python-ext-shields]][python-ext-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Python.Extensions][python-ext-shields-preview]][python-ext-nuget-preview] | An integration that contains some additional extensions for running python applications | +| - **Learn More**: [`Hosting.EventStore`][eventstore-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.EventStore][eventstore-shields]][eventstore-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.EventStore][eventstore-shields-preview]][eventstore-nuget-preview] | An Aspire hosting integration leveraging the [EventStore](https://eventstore.com) container. | +| - **Learn More**: [`EventStore`][eventstore-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.EventStore][eventstore-client-shields]][eventstore-client-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.EventStore][eventstore-client-shields-preview]][eventstore-client-nuget-preview] | An Aspire client integration for the [EventStore](https://github.com/EventStore/EventStore-Client-Dotnet) package. | +| - **Learn More**: [`Hosting.ActiveMQ`][activemq-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.ActiveMQ][activemq-shields]][activemq-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.ActiveMQ][activemq-shields-preview]][activemq-nuget-preview] | An Aspire hosting integration leveraging the [ActiveMq](https://activemq.apache.org) container. | +| - **Learn More**: [`Hosting.Sqlite`][sqlite-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Sqlite][sqlite-shields]][sqlite-hosting-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Sqlite][sqlite-shields-preview]][sqlite-hosting-nuget-preview] | An Aspire hosting integration to setup a SQLite database with optional SQLite Web as a dev UI. | +| - **Learn More**: [`Microsoft.Data.Sqlite`][sqlite-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Microsoft.Data.Sqlite][sqlite-shields]][sqlite-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Microsoft.Data.Sqlite][sqlite-shields-preview]][sqlite-nuget-preview] | An Aspire client integration for the Microsoft.Data.Sqlite NuGet package. | +| - **Learn More**: [`Microsoft.EntityFrameworkCore.Sqlite`][sqlite-ef-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Microsoft.EntityFrameworkCore.Sqlite][sqlite-ef-shields]][sqlite-ef-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Microsoft.EntityFrameworkCore.Sqlite][sqlite-ef-shields-preview]][sqlite-ef-nuget-preview] | An Aspire client integration for the Microsoft.EntityFrameworkCore.Sqlite NuGet package. | +| - **Learn More**: [`Hosting.Dapr`][dapr-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Dapr][dapr-shields]][dapr-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Dapr][dapr-shields-preview]][dapr-nuget-preview] | An Aspire hosting integration for Dapr. | +| - **Learn More**: [`Hosting.Azure.Dapr.Redis`][dapr-azureredis-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Azure.Dapr.Redis][dapr-azureredis-shields]][dapr-azureredis-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Azure.Dapr.Redis][dapr-azureredis-shields-preview]][dapr-azureredis-nuget-preview] | An extension for the Dapr hosting integration for using Dapr with Azure Redis cache. | +| - **Learn More**: [`Hosting.RavenDB`][ravendb-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.RavenDB][ravendb-shields]][ravendb-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.RavenDB][ravendb-shields-preview]][ravendb-nuget-preview] | An Aspire integration leveraging the [RavenDB](https://ravendb.net/) container. | +| - **Learn More**: [`RavenDB.Client`][ravendb-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.RavenDB.Client][ravendb-client-shields]][ravendb-client-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.RavenDB.Client][ravendb-client-shields-preview]][ravendb-client-nuget-preview] | An Aspire client integration for the [RavenDB.Client](https://www.nuget.org/packages/RavenDB.client) package. | +| - **Learn More**: [`Hosting.GoFeatureFlag`][go-feature-flag-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.GoFeatureFlag][go-feature-flag-shields]][go-feature-flag-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.GoFeatureFlag][go-feature-flag-shields-preview]][go-feature-flag-nuget-preview] | An Aspire hosting integration leveraging the [GoFeatureFlag](https://gofeatureflag.org/) container. | +| - **Learn More**: [`GoFeatureFlag`][go-feature-flag-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.GoFeatureFlag][go-feature-flag-client-shields]][go-feature-flag-client-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.GoFeatureFlag][go-feature-flag-client-shields-preview]][go-feature-flag-client-nuget-preview] | An Aspire client integration for the [GoFeatureFlag](https://github.com/open-feature/dotnet-sdk-contrib/tree/main/src/OpenFeature.Contrib.Providers.GOFeatureFlag) package. | +| - **Learn More**: [`Hosting.MongoDB.Extensions`][mongodb-ext-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.MongoDB.Extensions][mongodb-ext-shields]][mongodb-ext-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.MongoDB.Extensions][mongodb-ext-shields-preview]][mongodb-ext-nuget-preview] | An integration that contains some additional extensions for hosting MongoDB container. | +| - **Learn More**: [`Hosting.PostgreSQL.Extensions`][postgres-ext-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.PostgreSQL.Extensions][postgres-ext-shields]][postgres-ext-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.PostgreSQL.Extensions][postgres-ext-shields-preview]][postgres-ext-nuget-preview] | An integration that contains some additional extensions for hosting PostgreSQL container. | +| - **Learn More**: [`Hosting.Redis.Extensions`][redis-ext-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Redis.Extensions][redis-ext-shields]][redis-ext-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Redis.Extensions][redis-ext-shields-preview]][redis-ext-nuget-preview] | An integration that contains some additional extensions for hosting Redis container. | +| - **Learn More**: [`Hosting.SqlServer.Extensions`][sqlserver-ext-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.SqlServer.Extensions][sqlserver-ext-shields]][sqlserver-ext-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.SqlServer.Extensions][sqlserver-ext-shields-preview]][sqlserver-ext-nuget-preview] | An integration that contains some additional extensions for hosting SqlServer container. | +| - **Learn More**: [`Hosting.LavinMQ`][lavinmq-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.LavinMQ][lavinmq-shields]][lavinmq-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.LavinMQ][lavinmq-shields-preview]][lavinmq-nuget-preview] | An Aspire hosting integration for [LavinMQ](https://www.lavinmq.com). | +| - **Learn More**: [`Hosting.MailPit`][mailpit-ext-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.MailPit][mailpit-ext-shields]][mailpit-ext-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.MailPit][mailpit-ext-shields-preview]][mailpit-ext-nuget-preview] | An Aspire integration leveraging the [MailPit](https://mailpit.axllent.org/) container. | +| - **Learn More**: [`Hosting.k6`][k6-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.k6][k6-shields]][k6-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.k6][k6-shields-preview]][k6-nuget-preview] | An Aspire integration leveraging the [Grafana k6](https://k6.io/) container. | +| - **Learn More**: [`Hosting.MySql.Extensions`][mysql-ext-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.MySql.Extensions][mysql-ext-shields]][mysql-ext-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.MySql.Extensions][mysql-ext-shields-preview]][mysql-ext-nuget-preview] | An integration that contains some additional extensions for hosting MySql container. | +| - **Learn More**: [`Hosting.MinIO`][minio-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Minio][minio-hosting-shields]][minio-hosting-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Minio][minio-hosting-shields-preview]][minio-hosting-nuget-preview] | An Aspire hosting integration to setup a [MinIO S3](https://min.io/) storage. | +| - **Learn More**: [`MinIO.Client`][minio-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Minio.Client][minio-client-shields]][minio-client-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Client.Minio][minio-client-shields-preview]][minio-client-nuget-preview] | An Aspire client integration for the [MinIO](https://github.com/minio/minio-dotnet) package. | +| - **Learn More**: [`Hosting.SurrealDb`][surrealdb-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.SurrealDb][surrealdb-shields]][surrealdb-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.SurrealDb][surrealdb-shields-preview]][surrealdb-nuget-preview] | An Aspire hosting integration leveraging the [SurrealDB](https://surrealdb.com/) container. | +| - **Learn More**: [`SurrealDb`][surrealdb-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.SurrealDb][surrealdb-client-shields]][surrealdb-client-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.SurrealDb][surrealdb-client-shields-preview]][surrealdb-client-nuget-preview] | An Aspire client integration for the [SurrealDB](https://github.com/surrealdb/surrealdb.net/) package. | ## 🙌 Getting Started @@ -263,3 +265,12 @@ This project is supported by the [.NET Foundation](https://dotnetfoundation.org) [minio-client-nuget]: https://nuget.org/packages/CommunityToolkit.Aspire.Minio.Client/ [minio-client-shields-preview]: https://img.shields.io/nuget/vpre/CommunityToolkit.Aspire.Minio.Client?label=nuget%20(preview) [minio-client-nuget-preview]: https://nuget.org/packages/CommunityToolkit.Aspire.Minio.Client/absoluteLatest +[surrealdb-integration-docs]: https://learn.microsoft.com/dotnet/aspire/community-toolkit/hosting-surrealdb +[surrealdb-shields]: https://img.shields.io/nuget/v/CommunityToolkit.Aspire.Hosting.SurrealDb +[surrealdb-nuget]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.SurrealDb/ +[surrealdb-shields-preview]: https://img.shields.io/nuget/vpre/CommunityToolkit.Aspire.Hosting.SurrealDb?label=nuget%20(preview) +[surrealdb-nuget-preview]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.SurrealDb/absoluteLatest +[surrealdb-client-shields]: https://img.shields.io/nuget/v/CommunityToolkit.Aspire.SurrealDb +[surrealdb-client-nuget]: https://nuget.org/packages/CommunityToolkit.Aspire.SurrealDb/ +[surrealdb-client-shields-preview]: https://img.shields.io/nuget/vpre/CommunityToolkit.Aspire.SurrealDb?label=nuget%20(preview) +[surrealdb-client-nuget-preview]: https://nuget.org/packages/CommunityToolkit.Aspire.SurrealDb/absoluteLatest From 8e1cb5b489ae33e0c113905a1aef5926c6be9df6 Mon Sep 17 00:00:00 2001 From: Odonno Date: Thu, 14 Nov 2024 23:19:52 +0100 Subject: [PATCH 03/30] docs: create example projects --- CommunityToolkit.Aspire.slnx | 5 + Directory.Packages.props | 2 + ...Aspire.Hosting.SurrealDb.ApiService.csproj | 20 +++ .../Models/Todo.cs | 12 ++ .../Models/WeatherForecast.cs | 36 ++++++ .../Models/WeatherForecastFaker.cs | 34 +++++ .../Program.cs | 39 ++++++ .../Properties/launchSettings.json | 23 ++++ .../appsettings.json | 9 ++ ...it.Aspire.Hosting.SurrealDb.AppHost.csproj | 21 ++++ .../Program.cs | 13 ++ .../Properties/launchSettings.json | 29 +++++ .../appsettings.json | 9 ++ ...e.Hosting.SurrealDb.ServiceDefaults.csproj | 22 ++++ .../Extensions.cs | 117 ++++++++++++++++++ 15 files changed, 391 insertions(+) create mode 100644 examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService.csproj create mode 100644 examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/Todo.cs create mode 100644 examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/WeatherForecast.cs create mode 100644 examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/WeatherForecastFaker.cs create mode 100644 examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Program.cs create mode 100644 examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Properties/launchSettings.json create mode 100644 examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/appsettings.json create mode 100644 examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost.csproj create mode 100644 examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/Program.cs create mode 100644 examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/Properties/launchSettings.json create mode 100644 examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/appsettings.json create mode 100644 examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ServiceDefaults/CommunityToolkit.Aspire.Hosting.SurrealDb.ServiceDefaults.csproj create mode 100644 examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ServiceDefaults/Extensions.cs diff --git a/CommunityToolkit.Aspire.slnx b/CommunityToolkit.Aspire.slnx index 983fdf4a..2b0d51df 100644 --- a/CommunityToolkit.Aspire.slnx +++ b/CommunityToolkit.Aspire.slnx @@ -134,6 +134,11 @@ + + + + + diff --git a/Directory.Packages.props b/Directory.Packages.props index 088f36b6..57192622 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -20,6 +20,7 @@ + @@ -41,6 +42,7 @@ + diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService.csproj b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService.csproj new file mode 100644 index 00000000..2bf36ffe --- /dev/null +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/Todo.cs b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/Todo.cs new file mode 100644 index 00000000..c4981aa7 --- /dev/null +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/Todo.cs @@ -0,0 +1,12 @@ +using SurrealDb.Net.Models; + +namespace CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService.Models; + +public class Todo : Record +{ + internal const string Table = "todo"; + + public string? Title { get; set; } + public DateOnly? DueBy { get; set; } = null; + public bool IsComplete { get; set; } = false; +} \ No newline at end of file diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/WeatherForecast.cs b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/WeatherForecast.cs new file mode 100644 index 00000000..a90521be --- /dev/null +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/WeatherForecast.cs @@ -0,0 +1,36 @@ +using SurrealDb.Net.Models; + +namespace CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService.Models; + +/// +/// Weather forecast model. +/// +public class WeatherForecast : Record +{ + internal const string Table = "weatherForecast"; + + /// + /// Date of the weather forecast. + /// + public DateTime Date { get; set; } + + /// + /// Country of the weather forecast. + /// + public string? Country { get; set; } + + /// + /// Temperature in Celsius. + /// + public int TemperatureC { get; set; } + + /// + /// Temperature in Fahrenheit. + /// + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + /// + /// Summary of the weather forecast. + /// + public string? Summary { get; set; } +} \ No newline at end of file diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/WeatherForecastFaker.cs b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/WeatherForecastFaker.cs new file mode 100644 index 00000000..027fbe81 --- /dev/null +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/WeatherForecastFaker.cs @@ -0,0 +1,34 @@ +using Bogus; + +namespace CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService.Models; + +/// +/// Faker test class to generate fake WeatherForecast objects. +/// +public class WeatherForecastFaker : Faker +{ + private static readonly string[] Summaries = new[] + { + "Freezing", + "Bracing", + "Chilly", + "Cool", + "Mild", + "Warm", + "Balmy", + "Hot", + "Sweltering", + "Scorching" + }; + + /// + /// Constructor + /// + public WeatherForecastFaker() + { + RuleFor(o => o.Date, f => f.Date.Recent()); + RuleFor(o => o.Country, f => f.Address.Country()); + RuleFor(o => o.TemperatureC, f => f.Random.Number(-20, 55)); + RuleFor(o => o.Summary, f => f.Random.ArrayElement(Summaries)); + } +} \ No newline at end of file diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Program.cs b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Program.cs new file mode 100644 index 00000000..0f2ce474 --- /dev/null +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Program.cs @@ -0,0 +1,39 @@ +using CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService.Models; +using SurrealDb.Net; + +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); + +builder.AddSurrealClient("db", settings => +{ + // TODO : Wait for v0.7 + // settings.Options!.NamingPolicy = "CamelCase"; +}); + +var app = builder.Build(); + +app.MapDefaultEndpoints(); + +app.MapGroup("/api") + .MapSurrealEndpoints( + "/weatherForecast", + new() { EnableMutations = false } + ) + .MapSurrealEndpoints("/todo"); + +app.MapPost("/init", InitializeDbAsync); + +app.Run(); + +Task InitializeDbAsync(ISurrealDbClient surrealDbClient) +{ + const int initialCount = 5; + var weatherForecasts = new WeatherForecastFaker().Generate(initialCount); + + var tasks = weatherForecasts.Select(weatherForecast => + surrealDbClient.Create(WeatherForecast.Table, weatherForecast) + ); + + return Task.WhenAll(tasks); +} \ No newline at end of file diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Properties/launchSettings.json b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Properties/launchSettings.json new file mode 100644 index 00000000..5a2dd207 --- /dev/null +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5284", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7079;http://localhost:5284", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/appsettings.json b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost.csproj b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost.csproj new file mode 100644 index 00000000..d7535c8a --- /dev/null +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost.csproj @@ -0,0 +1,21 @@ + + + + + Exe + net8.0 + enable + enable + true + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/Program.cs b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/Program.cs new file mode 100644 index 00000000..9c28ac8c --- /dev/null +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/Program.cs @@ -0,0 +1,13 @@ +using Projects; + +var builder = DistributedApplication.CreateBuilder(args); + +var db = builder.AddSurrealServer("surreal") + .AddNamespace("ns") + .AddDatabase("db"); + +builder.AddProject("apiservice") + .WithReference(db) + .WaitFor(db); + +builder.Build().Run(); diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/Properties/launchSettings.json b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/Properties/launchSettings.json new file mode 100644 index 00000000..000b800c --- /dev/null +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:11918;http://localhost:37012", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:31745", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:12787" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15730", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19870", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20522" + } + } + } +} diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/appsettings.json b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/appsettings.json new file mode 100644 index 00000000..31c092aa --- /dev/null +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ServiceDefaults/CommunityToolkit.Aspire.Hosting.SurrealDb.ServiceDefaults.csproj b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ServiceDefaults/CommunityToolkit.Aspire.Hosting.SurrealDb.ServiceDefaults.csproj new file mode 100644 index 00000000..a546ce0b --- /dev/null +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ServiceDefaults/CommunityToolkit.Aspire.Hosting.SurrealDb.ServiceDefaults.csproj @@ -0,0 +1,22 @@ + + + + net8.0 + enable + enable + true + + + + + + + + + + + + + + + diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ServiceDefaults/Extensions.cs b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ServiceDefaults/Extensions.cs new file mode 100644 index 00000000..b34d7625 --- /dev/null +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ServiceDefaults/Extensions.cs @@ -0,0 +1,117 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ServiceDiscovery; +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Microsoft.Extensions.Hosting; +// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry. +// This project should be referenced by each service project in your solution. +// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults +public static class Extensions +{ + public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder) + { + builder.ConfigureOpenTelemetry(); + + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); + + // Uncomment the following to restrict the allowed schemes for service discovery. + // builder.Services.Configure(options => + // { + // options.AllowedSchemes = ["https"]; + // }); + + return builder; + } + + public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder) + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddRuntimeInstrumentation(); + }) + .WithTracing(tracing => + { + tracing.AddAspNetCoreInstrumentation() + // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package) + //.AddGrpcClientInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder) + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.AddOpenTelemetry().UseOtlpExporter(); + } + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) + //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) + //{ + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + //} + + return builder; + } + + public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicationBuilder builder) + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Adding health checks endpoints to applications in non-development environments has security implications. + // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. + if (app.Environment.IsDevelopment()) + { + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks("/health"); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks("/alive", new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + } + + return app; + } +} From a20eb9f7778144170048410690940f1d65583b1b Mon Sep 17 00:00:00 2001 From: Odonno Date: Fri, 15 Nov 2024 14:50:19 +0100 Subject: [PATCH 04/30] test: add tests for surrealdb integration --- CommunityToolkit.Aspire.slnx | 2 + .../AddSurrealServerTests.cs | 183 ++++++++++++ .../AppHostTests.cs | 46 +++ ...lkit.Aspire.Hosting.SurrealDb.Tests.csproj | 24 ++ .../SurrealDbFunctionalTests.cs | 277 ++++++++++++++++++ .../SurrealDbPublicApiTests.cs | 247 ++++++++++++++++ .../AspireSurrealClientExtensionsTest.cs | 142 +++++++++ ...unityToolkit.Aspire.SurrealDb.Tests.csproj | 21 ++ .../ConfigurationTests.cs | 27 ++ .../ConformanceTests.cs | 106 +++++++ .../SurrealDbClientPublicApiTests.cs | 87 ++++++ .../SurrealDbContainerFixture.cs | 56 ++++ 12 files changed, 1218 insertions(+) create mode 100644 tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AddSurrealServerTests.cs create mode 100644 tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AppHostTests.cs create mode 100644 tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests.csproj create mode 100644 tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbFunctionalTests.cs create mode 100644 tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbPublicApiTests.cs create mode 100644 tests/CommunityToolkit.Aspire.SurrealDb.Tests/AspireSurrealClientExtensionsTest.cs create mode 100644 tests/CommunityToolkit.Aspire.SurrealDb.Tests/CommunityToolkit.Aspire.SurrealDb.Tests.csproj create mode 100644 tests/CommunityToolkit.Aspire.SurrealDb.Tests/ConfigurationTests.cs create mode 100644 tests/CommunityToolkit.Aspire.SurrealDb.Tests/ConformanceTests.cs create mode 100644 tests/CommunityToolkit.Aspire.SurrealDb.Tests/SurrealDbClientPublicApiTests.cs create mode 100644 tests/CommunityToolkit.Aspire.SurrealDb.Tests/SurrealDbContainerFixture.cs diff --git a/CommunityToolkit.Aspire.slnx b/CommunityToolkit.Aspire.slnx index 2b0d51df..1dd9b4a3 100644 --- a/CommunityToolkit.Aspire.slnx +++ b/CommunityToolkit.Aspire.slnx @@ -224,12 +224,14 @@ + + diff --git a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AddSurrealServerTests.cs b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AddSurrealServerTests.cs new file mode 100644 index 00000000..52175523 --- /dev/null +++ b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AddSurrealServerTests.cs @@ -0,0 +1,183 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting; +using Aspire.Hosting.Utils; +using System.Net.Sockets; + +namespace CommunityToolkit.Aspire.Hosting.SurrealDb.Tests; + +public class AddSurrealServerTests +{ + [Fact] + public void AddSurrealServerAddsGeneratedPasswordParameterWithUserSecretsParameterDefaultInRunMode() + { + using var appBuilder = TestDistributedApplicationBuilder.Create(); + + var surrealServer = appBuilder.AddSurrealServer("surreal"); + + Assert.Equal("Aspire.Hosting.ApplicationModel.UserSecretsParameterDefault", surrealServer.Resource.PasswordParameter.Default?.GetType().FullName); + } + + [Fact] + public void AddSurrealServerDoesNotAddGeneratedPasswordParameterWithUserSecretsParameterDefaultInPublishMode() + { + using var appBuilder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish); + + var surrealServer = appBuilder.AddSurrealServer("surreal"); + + Assert.NotEqual("Aspire.Hosting.ApplicationModel.UserSecretsParameterDefault", surrealServer.Resource.PasswordParameter.Default?.GetType().FullName); + } + + [Fact] + public async Task AddSurrealServerContainerWithDefaultsAddsAnnotationMetadata() + { + var appBuilder = DistributedApplication.CreateBuilder(); + + var surrealServer = appBuilder.AddSurrealServer("surreal"); + + using var app = appBuilder.Build(); + + var appModel = app.Services.GetRequiredService(); + + var containerResource = Assert.Single(appModel.Resources.OfType()); + Assert.Equal("surreal", containerResource.Name); + + var endpoint = Assert.Single(containerResource.Annotations.OfType()); + Assert.Equal(8000, endpoint.TargetPort); + Assert.False(endpoint.IsExternal); + Assert.Equal("tcp", endpoint.Name); + Assert.Null(endpoint.Port); + Assert.Equal(ProtocolType.Tcp, endpoint.Protocol); + Assert.Equal("tcp", endpoint.Transport); + Assert.Equal("tcp", endpoint.UriScheme); + + var containerAnnotation = Assert.Single(containerResource.Annotations.OfType()); + Assert.Equal(SurrealDbContainerImageTags.Tag, containerAnnotation.Tag); + Assert.Equal(SurrealDbContainerImageTags.Image, containerAnnotation.Image); + Assert.Equal(SurrealDbContainerImageTags.Registry, containerAnnotation.Registry); + + var config = await surrealServer.Resource.GetEnvironmentVariableValuesAsync(); + + Assert.Collection(config, + env => + { + Assert.Equal("SURREAL_USER", env.Key); + Assert.NotNull(env.Value); + }, + env => + { + Assert.Equal("SURREAL_PASS", env.Key); + Assert.NotNull(env.Value); + Assert.True(env.Value.Length >= 8); + }); + } + + [Fact] + public async Task SurrealServerCreatesConnectionString() + { + var appBuilder = DistributedApplication.CreateBuilder(); + appBuilder.Configuration["Parameters:pass"] = "p@ssw0rd1"; + + var pass = appBuilder.AddParameter("pass"); + appBuilder + .AddSurrealServer("surreal", null, pass) + .WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 8000)); + + using var app = appBuilder.Build(); + + var appModel = app.Services.GetRequiredService(); + + var connectionStringResource = Assert.Single(appModel.Resources.OfType()); + var connectionString = await connectionStringResource.GetConnectionStringAsync(default); + + Assert.Equal("Server=ws://127.0.0.1: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] + public async Task SurrealServerDatabaseCreatesConnectionString() + { + var appBuilder = DistributedApplication.CreateBuilder(); + appBuilder.Configuration["Parameters:pass"] = "p@ssw0rd1"; + + var pass = appBuilder.AddParameter("pass"); + appBuilder + .AddSurrealServer("surreal", null, pass) + .WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 8000)) + .AddNamespace("ns", "myns") + .AddDatabase("db", "mydb"); + + using var app = appBuilder.Build(); + + var appModel = app.Services.GetRequiredService(); + + var surrealResource = Assert.Single(appModel.Resources.OfType()); + var connectionStringResource = (IResourceWithConnectionString)surrealResource; + var connectionString = await connectionStringResource.GetConnectionStringAsync(); + + Assert.Equal("Server=ws://127.0.0.1:8000/rpc;User=root;Password=p@ssw0rd1;Namespace=myns;Database=mydb", connectionString); + Assert.Equal("{ns.connectionString};Database=mydb", connectionStringResource.ConnectionStringExpression.ValueExpression); + } + + [Fact] + public void ThrowsWithIdenticalChildResourceNames() + { + using var builder = TestDistributedApplicationBuilder.Create(); + + var db = builder.AddSurrealServer("surreal1"); + db.AddNamespace("ns").AddDatabase("db"); + + Assert.Throws(() => db.AddNamespace("ns").AddDatabase("db")); + } + + [Fact] + public void ThrowsWithIdenticalChildResourceNamesDifferentParents() + { + using var builder = TestDistributedApplicationBuilder.Create(); + + builder.AddSurrealServer("surreal1") + .AddNamespace("ns") + .AddDatabase("db"); + + var db = builder.AddSurrealServer("surreal2"); + Assert.Throws(() => db.AddNamespace("ns").AddDatabase("db")); + } + + [Fact] + public void CanAddDatabasesWithDifferentNamesOnSingleServerAndNamespace() + { + using var builder = TestDistributedApplicationBuilder.Create(); + + var surrealNs = builder.AddSurrealServer("surreal1").AddNamespace("ns"); + + var db1 = surrealNs.AddDatabase("db1", "customers1"); + var db2 = surrealNs.AddDatabase("db2", "customers2"); + + Assert.Equal("customers1", db1.Resource.DatabaseName); + Assert.Equal("customers2", db2.Resource.DatabaseName); + + Assert.Equal("{ns.connectionString};Database=customers1", db1.Resource.ConnectionStringExpression.ValueExpression); + Assert.Equal("{ns.connectionString};Database=customers2", db2.Resource.ConnectionStringExpression.ValueExpression); + } + + [Fact] + public void CanAddDatabasesWithTheSameNameOnMultipleServers() + { + using var builder = TestDistributedApplicationBuilder.Create(); + + var db1 = builder.AddSurrealServer("surreal1") + .AddNamespace("ns1", "ns") + .AddDatabase("db1", "imports"); + + var db2 = builder.AddSurrealServer("surreal2") + .AddNamespace("ns2", "ns") + .AddDatabase("db2", "imports"); + + Assert.Equal("imports", db1.Resource.DatabaseName); + Assert.Equal("imports", db2.Resource.DatabaseName); + + Assert.Equal("{ns1.connectionString};Database=imports", db1.Resource.ConnectionStringExpression.ValueExpression); + Assert.Equal("{ns2.connectionString};Database=imports", db2.Resource.ConnectionStringExpression.ValueExpression); + } +} \ No newline at end of file diff --git a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AppHostTests.cs b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AppHostTests.cs new file mode 100644 index 00000000..16936db8 --- /dev/null +++ b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AppHostTests.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using CommunityToolkit.Aspire.Testing; +using Aspire.Components.Common.Tests; +using FluentAssertions; +using System.Net.Http.Json; + +namespace CommunityToolkit.Aspire.Hosting.SurrealDb.Tests; + +[RequiresDocker] +public class AppHostTests(AspireIntegrationTestFixture fixture) : IClassFixture> +{ + [Fact] + public async Task SurrealResourceStartsAndRespondsOk() + { + const string resourceName = "db"; + await fixture.ResourceNotificationService.WaitForResourceAsync(resourceName, KnownResourceStates.Running).WaitAsync(TimeSpan.FromMinutes(1)); + var httpClient = fixture.CreateHttpClient(resourceName); + + var response = await httpClient.GetAsync("/"); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + } + + [Fact] + public async Task ApiServiceStartsAndRespondsOk() + { + const string resourceName = "apiservice"; + await fixture.ResourceNotificationService.WaitForResourceAsync(resourceName, KnownResourceStates.Running).WaitAsync(TimeSpan.FromMinutes(1)); + var httpClient = fixture.CreateHttpClient(resourceName); + + var todoResponse = await httpClient.GetAsync("/api/todo"); + todoResponse.StatusCode.Should().Be(HttpStatusCode.OK); + + var initResponse = await httpClient.PostAsync("/init", null); + initResponse.StatusCode.Should().Be(HttpStatusCode.OK); + + var weatherForecastResponse = await httpClient.GetAsync("/api/weatherForecast"); + weatherForecastResponse.StatusCode.Should().Be(HttpStatusCode.OK); + + var data = await weatherForecastResponse.Content.ReadFromJsonAsync>(); + + data.Should().NotBeNullOrEmpty(); + } +} \ No newline at end of file diff --git a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests.csproj b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests.csproj new file mode 100644 index 00000000..b269b0b4 --- /dev/null +++ b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests.csproj @@ -0,0 +1,24 @@ + + + + CommunityToolkit.Aspire.Hosting.SurrealDb.Tests + + + + + + + + + + + + + + + + + + + + diff --git a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbFunctionalTests.cs b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbFunctionalTests.cs new file mode 100644 index 00000000..576a157e --- /dev/null +++ b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbFunctionalTests.cs @@ -0,0 +1,277 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Components.Common.Tests; +using Aspire.Hosting; +using Aspire.Hosting.Utils; +using Bogus; +using CommunityToolkit.Aspire.Testing; +using FluentAssertions; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Hosting; +using SurrealDb.Net; +using Xunit.Abstractions; +using SurrealRecord = SurrealDb.Net.Models.Record; + +namespace CommunityToolkit.Aspire.Hosting.SurrealDb.Tests; + +[RequiresDocker] +public class SurrealDbFunctionalTests(ITestOutputHelper testOutputHelper) +{ + private const int _generatedTodoCount = 10; + private static readonly Todo[] _todoList; + + static SurrealDbFunctionalTests() + { + _todoList = new TodoFaker().Generate(_generatedTodoCount).ToArray(); + + int index = 0; + foreach (var todo in _todoList) + { + todo.Id = (Todo.Table, (++index).ToString()); + } + } + + [Fact] + public async Task VerifySurrealDbResource() + { + using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); + + var surrealServer = builder.AddSurrealServer("surreal"); + + var db = surrealServer + .AddNamespace("ns") + .AddDatabase("db"); + + using var app = builder.Build(); + + await app.StartAsync(); + +#pragma warning disable CTASPIRE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + await app.WaitForTextAsync("Started web server on", surrealServer.Resource.Name); +#pragma warning restore CTASPIRE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + + var hb = Host.CreateApplicationBuilder(); + + hb.Configuration[$"ConnectionStrings:{db.Resource.Name}"] = await db.Resource.ConnectionStringExpression.GetValueAsync(default); + + hb.AddSurrealClient(db.Resource.Name); + + using var host = hb.Build(); + + await host.StartAsync(); + + var surrealDbClient = host.Services.GetRequiredService(); + + await CreateTestData(surrealDbClient); + await AssertTestData(surrealDbClient); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume) + { + string? volumeName = null; + string? bindMountPath = null; + + try + { + using var builder1 = TestDistributedApplicationBuilder.Create(testOutputHelper); + + var surrealServer1 = builder1.AddSurrealServer("surreal"); + + var db1 = surrealServer1 + .AddNamespace("ns") + .AddDatabase("db"); + + if (useVolume) + { + // Use a deterministic volume name to prevent them from exhausting the machines if deletion fails +#pragma warning disable CTASPIRE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + volumeName = VolumeNameGenerator.CreateVolumeName(surrealServer1, nameof(WithDataShouldPersistStateBetweenUsages)); +#pragma warning restore CTASPIRE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + + // if the volume already exists (because of a crashing previous run), delete it + DockerUtils.AttemptDeleteDockerVolume(volumeName, throwOnFailure: true); + surrealServer1.WithDataVolume(volumeName); + } + else + { + bindMountPath = Directory.CreateTempSubdirectory().FullName; + surrealServer1.WithDataBindMount(bindMountPath); + } + + using (var app = builder1.Build()) + { + await app.StartAsync(); + +#pragma warning disable CTASPIRE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + await app.WaitForTextAsync("Started web server on", surrealServer1.Resource.Name); +#pragma warning restore CTASPIRE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + + try + { + var hb = Host.CreateApplicationBuilder(); + + hb.Configuration[$"ConnectionStrings:{db1.Resource.Name}"] = await db1.Resource.ConnectionStringExpression.GetValueAsync(default); + + hb.AddSurrealClient(db1.Resource.Name); + + using (var host = hb.Build()) + { + await host.StartAsync(); + + var surrealDbClient = host.Services.GetRequiredService(); + await CreateTestData(surrealDbClient); + await AssertTestData(surrealDbClient); + } + } + finally + { + // Stops the container, or the Volume would still be in use + await app.StopAsync(); + } + } + + using var builder2 = TestDistributedApplicationBuilder.Create(testOutputHelper); + + var surrealServer2 = builder2.AddSurrealServer("surreal"); + + var db2 = surrealServer2 + .AddNamespace("ns") + .AddDatabase("db"); + + if (useVolume) + { + surrealServer2.WithDataVolume(volumeName); + } + else + { + surrealServer2.WithDataBindMount(bindMountPath!); + } + + using (var app = builder2.Build()) + { + await app.StartAsync(); + +#pragma warning disable CTASPIRE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + await app.WaitForTextAsync("Started web server on", surrealServer2.Resource.Name); +#pragma warning restore CTASPIRE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + + try + { + var hb = Host.CreateApplicationBuilder(); + + hb.Configuration[$"ConnectionStrings:{db2.Resource.Name}"] = await db2.Resource.ConnectionStringExpression.GetValueAsync(default); + + hb.AddSurrealClient(db2.Resource.Name); + + using (var host = hb.Build()) + { + await host.StartAsync(); + var surrealDbClient = host.Services.GetRequiredService(); + await AssertTestData(surrealDbClient); + } + } + finally + { + // Stops the container, or the Volume would still be in use + await app.StopAsync(); + } + } + + } + finally + { + if (volumeName is not null) + { + DockerUtils.AttemptDeleteDockerVolume(volumeName); + } + + if (bindMountPath is not null) + { + try + { + Directory.Delete(bindMountPath, recursive: true); + } + catch + { + // Don't fail test if we can't clean the temporary folder + } + } + } + } + + [Fact] + public async Task VerifyWaitForOnSurrealDbBlocksDependentResources() + { + var cts = new CancellationTokenSource(TimeSpan.FromMinutes(10)); + using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); + + var healthCheckTcs = new TaskCompletionSource(); + builder.Services.AddHealthChecks().AddAsyncCheck("blocking_check", () => + { + return healthCheckTcs.Task; + }); + + var resource = builder.AddSurrealServer("resource") + .WithHealthCheck("blocking_check"); + + var dependentResource = builder.AddSurrealServer("dependentresource") + .WaitFor(resource); + + using var app = builder.Build(); + + var pendingStart = app.StartAsync(cts.Token); + + var rns = app.Services.GetRequiredService(); + + await rns.WaitForResourceAsync(resource.Resource.Name, KnownResourceStates.Running, cts.Token); + + await rns.WaitForResourceAsync(dependentResource.Resource.Name, KnownResourceStates.Waiting, cts.Token); + + healthCheckTcs.SetResult(HealthCheckResult.Healthy()); + + await rns.WaitForResourceAsync(resource.Resource.Name, re => re.Snapshot.HealthStatus == HealthStatus.Healthy, cts.Token); + + await rns.WaitForResourceAsync(dependentResource.Resource.Name, KnownResourceStates.Running, cts.Token); + + await pendingStart; + + await app.StopAsync(); + } + + private static async Task CreateTestData(SurrealDbClient surrealDbClient) + { + await surrealDbClient.Insert(Todo.Table, _todoList); + } + + private static async Task AssertTestData(SurrealDbClient surrealDbClient) + { + var records = await surrealDbClient.Select(Todo.Table); + records.Count().Should().Be(_generatedTodoCount); + + var firstRecord = await surrealDbClient.Select((Todo.Table, "1")); + firstRecord.Should().BeEquivalentTo(_todoList[0]); + } + + private sealed class Todo : SurrealRecord + { + internal const string Table = "todo"; + + public string? Title { get; set; } + public DateOnly? DueBy { get; set; } = null; + public bool IsComplete { get; set; } = false; + } + + private class TodoFaker : Faker + { + public TodoFaker() + { + RuleFor(o => o.Title, f => f.Lorem.Sentence()); + RuleFor(o => o.DueBy, f => f.Date.SoonDateOnly()); + RuleFor(o => o.IsComplete, f => f.Random.Bool()); + } + } +} \ No newline at end of file diff --git a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbPublicApiTests.cs b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbPublicApiTests.cs new file mode 100644 index 00000000..159e7425 --- /dev/null +++ b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbPublicApiTests.cs @@ -0,0 +1,247 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting; + +namespace CommunityToolkit.Aspire.Hosting.SurrealDb.Tests; + +public class SurrealDbPublicApiTests +{ + [Fact] + public void AddSurrealServerContainerShouldThrowWhenBuilderIsNull() + { + IDistributedApplicationBuilder builder = null!; + const string name = "surreal"; + + var action = () => builder.AddSurrealServer(name); + + var exception = Assert.Throws(action); + Assert.Equal(nameof(builder), exception.ParamName); + } + + [Fact] + public void AddSurrealServerContainerShouldThrowWhenNameIsNull() + { + var builder = DistributedApplication.CreateBuilder([]); + string name = null!; + + var action = () => builder.AddSurrealServer(name); + + var exception = Assert.Throws(action); + Assert.Equal(nameof(name), exception.ParamName); + } + + [Fact] + public void AddDatabaseShouldThrowWhenBuilderIsNull() + { + IResourceBuilder builder = null!; + const string name = "surreal"; + + var action = () => builder.AddDatabase(name); + + var exception = Assert.Throws(action); + Assert.Equal(nameof(builder), exception.ParamName); + } + + [Fact] + public void AddDatabaseShouldThrowWhenNameIsNull() + { + var builder = DistributedApplication.CreateBuilder([]) + .AddSurrealServer("surreal") + .AddNamespace("ns"); + string name = null!; + + var action = () => builder.AddDatabase(name); + + var exception = Assert.Throws(action); + Assert.Equal(nameof(name), exception.ParamName); + } + + [Fact] + public void AddDatabaseShouldThrowWhenNameIsEmpty() + { + var builder = DistributedApplication.CreateBuilder([]) + .AddSurrealServer("surreal") + .AddNamespace("ns"); + string name = ""; + + var action = () => builder.AddDatabase(name); + + var exception = Assert.Throws(action); + Assert.Equal(nameof(name), exception.ParamName); + } + + [Fact] + public void WithDataVolumeShouldThrowWhenBuilderIsNull() + { + IResourceBuilder builder = null!; + + var action = () => builder.WithDataVolume(); + + var exception = Assert.Throws(action); + Assert.Equal(nameof(builder), exception.ParamName); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void WithDataShouldThrowWhenBuilderIsNull(bool useVolume) + { + IResourceBuilder builder = null!; + + Func>? action = null; + + if (useVolume) + { + action = () => builder.WithDataVolume(); + } + else + { + const string source = "/data"; + + action = () => builder.WithDataBindMount(source); + } + + var exception = Assert.Throws(action); + Assert.Equal(nameof(builder), exception.ParamName); + } + + [Fact] + public void WithDataBindMountShouldThrowWhenSourceIsNull() + { + var builder = new DistributedApplicationBuilder([]); + var resourceBuilder = builder.AddSurrealServer("surreal"); + + string source = null!; + + var action = () => resourceBuilder.WithDataBindMount(source); + + var exception = Assert.Throws(action); + Assert.Equal(nameof(source), exception.ParamName); + } + + [Fact] + public void CtorSurrealServerServerResourceShouldThrowWhenNameIsNull() + { + var distributedApplicationBuilder = DistributedApplication.CreateBuilder([]); + string name = null!; + const string key = nameof(key); + var password = ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(distributedApplicationBuilder, key, special: false); + + var action = () => new SurrealDbServerResource(name, null, password); + + var exception = Assert.Throws(action); + Assert.Equal(nameof(name), exception.ParamName); + } + + [Fact] + public void CtorSurrealServerDatabaseResourceShouldThrowWhenNameIsNull() + { + var distributedApplicationBuilder = DistributedApplication.CreateBuilder([]); + + string name = null!; + string namespaceName = "ns1"; + string databaseName = "db1"; + var password = ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(distributedApplicationBuilder, "password", special: false); + var parent = new SurrealDbServerResource("surreal", null, password); + var nsParent = new SurrealDbNamespaceResource("ns", namespaceName, parent); + var action = () => new SurrealDbDatabaseResource(name, databaseName, nsParent); + + var exception = Assert.Throws(action); + Assert.Equal(nameof(name), exception.ParamName); + } + + [Fact] + public void CtorSurrealServerDatabaseResourceShouldThrowWhenNameIsEmpty() + { + var distributedApplicationBuilder = DistributedApplication.CreateBuilder([]); + + string name = ""; + string namespaceName = "ns1"; + string databaseName = "db1"; + var password = ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(distributedApplicationBuilder, "password", special: false); + var parent = new SurrealDbServerResource("surreal", null, password); + var nsParent = new SurrealDbNamespaceResource("ns", namespaceName, parent); + var action = () => new SurrealDbDatabaseResource(name, databaseName, nsParent); + + var exception = Assert.Throws(action); + Assert.Equal(nameof(name), exception.ParamName); + } + + [Fact] + public void CtorSurrealServerNamespaceResourceShouldThrowWhenNamespaceNameIsNull() + { + var distributedApplicationBuilder = DistributedApplication.CreateBuilder([]); + + string namespaceName = null!; + var password = ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(distributedApplicationBuilder, "password", special: false); + var parent = new SurrealDbServerResource("surreal", null, password); + var action = () => new SurrealDbNamespaceResource("ns", namespaceName, parent); + + var exception = Assert.Throws(action); + Assert.Equal(nameof(namespaceName), exception.ParamName); + } + + [Fact] + public void CtorSurrealServerNamespaceResourceShouldThrowWhenNamespaceNameIsEmpty() + { + var distributedApplicationBuilder = DistributedApplication.CreateBuilder([]); + + string namespaceName = ""; + var password = ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(distributedApplicationBuilder, "password", special: false); + var parent = new SurrealDbServerResource("surreal", null, password); + var action = () => new SurrealDbNamespaceResource("ns", namespaceName, parent); + + var exception = Assert.Throws(action); + Assert.Equal(nameof(namespaceName), exception.ParamName); + } + + [Fact] + public void CtorSurrealServerDatabaseResourceShouldThrowWhenDatabaseNameIsNull() + { + var distributedApplicationBuilder = DistributedApplication.CreateBuilder([]); + + string name = "surreal"; + string namespaceName = "ns"; + string databaseName = null!; + var password = ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(distributedApplicationBuilder, "password", special: false); + var parent = new SurrealDbServerResource("surreal", null, password); + var nsParent = new SurrealDbNamespaceResource("ns", namespaceName, parent); + var action = () => new SurrealDbDatabaseResource(name, databaseName, nsParent); + + var exception = Assert.Throws(action); + Assert.Equal(nameof(databaseName), exception.ParamName); + } + + [Fact] + public void CtorSurrealServerDatabaseResourceShouldThrowWhenDatabaseNameIsEmpty() + { + var distributedApplicationBuilder = DistributedApplication.CreateBuilder([]); + + string name = "surreal"; + string namespaceName = "ns"; + string databaseName = null!; + var password = ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(distributedApplicationBuilder, "password", special: false); + var parent = new SurrealDbServerResource("surreal", null, password); + var nsParent = new SurrealDbNamespaceResource("ns", namespaceName, parent); + var action = () => new SurrealDbDatabaseResource(name, databaseName, nsParent); + + var exception = Assert.Throws(action); + Assert.Equal(nameof(databaseName), exception.ParamName); + } + + [Fact] + public void CtorSurrealServerDatabaseResourceShouldThrowWhenParentIsNull() + { + var distributedApplicationBuilder = DistributedApplication.CreateBuilder([]); + + string name = "surreal"; + string databaseName = "db1"; + var password = ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(distributedApplicationBuilder, "password", special: false); + SurrealDbNamespaceResource parent = null!; + var action = () => new SurrealDbDatabaseResource(name, databaseName, parent); + + var exception = Assert.Throws(action); + Assert.Equal(nameof(parent), exception.ParamName); + } +} diff --git a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/AspireSurrealClientExtensionsTest.cs b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/AspireSurrealClientExtensionsTest.cs new file mode 100644 index 00000000..e179b532 --- /dev/null +++ b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/AspireSurrealClientExtensionsTest.cs @@ -0,0 +1,142 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Components.Common.Tests; +using FluentAssertions; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Hosting; +using SurrealDb.Net; + +namespace CommunityToolkit.Aspire.SurrealDb.Tests; + +public class AspireSurrealClientExtensionsTest(SurrealDbContainerFixture containerFixture) : IClassFixture +{ + private const string DefaultConnectionName = "db"; + + private string DefaultConnectionString => + RequiresDockerAttribute.IsSupported ? containerFixture.GetConnectionString() : "http://localhost:27011"; + + [Theory] + [InlineData(true)] + [InlineData(false)] + [RequiresDocker] + public async Task AddSurrealClient_HealthCheckShouldBeRegisteredWhenEnabled(bool useKeyed) + { + var key = DefaultConnectionName; + + var builder = CreateBuilder(DefaultConnectionString); + + if (useKeyed) + { + builder.AddKeyedSurrealClient(key, settings => + { + settings.DisableHealthChecks = false; + }); + } + else + { + builder.AddSurrealClient(DefaultConnectionName, settings => + { + settings.DisableHealthChecks = false; + }); + } + + using var host = builder.Build(); + + var healthCheckService = host.Services.GetRequiredService(); + + var healthCheckReport = await healthCheckService.CheckHealthAsync(); + + var healthCheckName = useKeyed ? $"surrealdb_{key}" : "surrealdb"; + + Assert.Contains(healthCheckReport.Entries, x => x.Key == healthCheckName); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void AddSurrealClient_HealthCheckShouldNotBeRegisteredWhenDisabled(bool useKeyed) + { + var builder = CreateBuilder(DefaultConnectionString); + + if (useKeyed) + { + builder.AddKeyedSurrealClient(DefaultConnectionName, settings => + { + settings.DisableHealthChecks = true; + }); + } + else + { + builder.AddSurrealClient(DefaultConnectionName, settings => + { + settings.DisableHealthChecks = true; + }); + } + + using var host = builder.Build(); + + var healthCheckService = host.Services.GetService(); + + healthCheckService.Should().BeNull(); + } + + [Fact] + public void CanAddMultipleKeyedServices() + { + var builder = Host.CreateEmptyApplicationBuilder(null); + + builder.Configuration.AddInMemoryCollection([ + new KeyValuePair("ConnectionStrings:surreal1", "http://localhost:19530"), + new KeyValuePair("ConnectionStrings:surreal2", "http://localhost:19531"), + new KeyValuePair("ConnectionStrings:surreal3", "http://localhost:19532"), + ]); + + builder.AddSurrealClient("surreal1"); + builder.AddKeyedSurrealClient("surreal2"); + builder.AddKeyedSurrealClient("surreal3"); + + using var host = builder.Build(); + + var client1 = host.Services.GetRequiredService(); + var client2 = host.Services.GetRequiredKeyedService("surreal2"); + var client3 = host.Services.GetRequiredKeyedService("surreal3"); + + Assert.NotSame(client1, client2); + Assert.NotSame(client1, client3); + Assert.NotSame(client2, client3); + } + + [Fact] + public void CanAddClientFromEncodedConnectionString() + { + var builder = Host.CreateEmptyApplicationBuilder(null); + + builder.Configuration.AddInMemoryCollection([ + new KeyValuePair("ConnectionStrings:surreal1", "Endpoint=http://localhost:19530"), + new KeyValuePair("ConnectionStrings:surreal2", "Endpoint=http://localhost:19531"), + ]); + + builder.AddSurrealClient("surreal1"); + builder.AddKeyedSurrealClient("surreal2"); + + using var host = builder.Build(); + + var client1 = host.Services.GetRequiredService(); + var client2 = host.Services.GetRequiredKeyedService("surreal2"); + + Assert.NotSame(client1, client2); + } + + private static HostApplicationBuilder CreateBuilder(string connectionString) + { + var builder = Host.CreateEmptyApplicationBuilder(null); + + builder.Configuration.AddInMemoryCollection([ + new KeyValuePair($"ConnectionStrings:{DefaultConnectionName}", connectionString) + ]); + + return builder; + } +} \ No newline at end of file diff --git a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/CommunityToolkit.Aspire.SurrealDb.Tests.csproj b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/CommunityToolkit.Aspire.SurrealDb.Tests.csproj new file mode 100644 index 00000000..c5746083 --- /dev/null +++ b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/CommunityToolkit.Aspire.SurrealDb.Tests.csproj @@ -0,0 +1,21 @@ + + + + CommunityToolkit.Aspire.SurrealDb.Tests + + + + + + + + + + + + + + + + + diff --git a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/ConfigurationTests.cs b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/ConfigurationTests.cs new file mode 100644 index 00000000..08437b71 --- /dev/null +++ b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/ConfigurationTests.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using FluentAssertions; + +namespace CommunityToolkit.Aspire.SurrealDb.Tests; + +public class ConfigurationTests +{ + [Fact] + public void HealthChecksEnabledByDefault() + { + new SurrealDbClientSettings().DisableHealthChecks.Should().BeFalse(); + } + + [Fact] + public void OptionsAreNullByDefault() + { + new SurrealDbClientSettings().Options.Should().BeNull(); + } + + [Fact] + public void LifetimeIsSingletonByDefault() + { + new SurrealDbClientSettings().Lifetime.Should().Be(ServiceLifetime.Singleton); + } +} diff --git a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/ConformanceTests.cs b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/ConformanceTests.cs new file mode 100644 index 00000000..b237d303 --- /dev/null +++ b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/ConformanceTests.cs @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Components.Common.Tests; +using Aspire.Components.ConformanceTests; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using SurrealDb.Net; + +namespace CommunityToolkit.Aspire.SurrealDb.Tests; + +public class ConformanceTests : + ConformanceTests, + IClassFixture +{ + private readonly SurrealDbContainerFixture _containerFixture; + + protected override ServiceLifetime ServiceLifetime => ServiceLifetime.Singleton; + + protected override string ActivitySourceName => string.Empty; + + protected override string[] RequiredLogCategories => []; + + protected override bool CanConnectToServer => RequiresDockerAttribute.IsSupported; + + protected override bool SupportsKeyedRegistrations => true; + + public ConformanceTests(SurrealDbContainerFixture containerFixture) + { + _containerFixture = containerFixture; + } + + protected override void PopulateConfiguration(ConfigurationManager configuration, string? key = null) + { + var connectionString = RequiresDockerAttribute.IsSupported ? + $"{_containerFixture.GetConnectionString()}" : + "Endpoint=http://localhost:27017"; + + configuration.AddInMemoryCollection( + [ + new KeyValuePair(CreateConfigKey("Aspire:Surreal:Client", key, "Endpoint"), GetConnectionStringKeyValue(connectionString,"Endpoint")), + new KeyValuePair($"ConnectionStrings:{key}", $"{connectionString}") + ]); + } + + internal static string GetConnectionStringKeyValue(string connectionString, string configKey) + { + // from the connection string, extract the key value of the configKey + var parts = connectionString.Split(';'); + foreach (var part in parts) + { + var keyValue = part.Split('='); + if (keyValue.Length == 2 && keyValue[0].Equals(configKey, StringComparison.OrdinalIgnoreCase)) + { + return keyValue[1]; + } + } + return string.Empty; + } + + protected override void RegisterComponent(HostApplicationBuilder builder, Action? configure = null, string? key = null) + { + if (key is null) + { + builder.AddSurrealClient("surreal", configureSettings: configure); + } + else + { + builder.AddKeyedSurrealClient(key, configureSettings: configure); + } + } + + protected override string ValidJsonConfig => """ + { + "Aspire": { + "Surreal": { + "Client": { + "Endpoint": "http://localhost:19530" + } + } + } + } + """; + + protected override void SetHealthCheck(SurrealDbClientSettings options, bool enabled) + { + options.DisableHealthChecks = !enabled; + } + + protected override void SetMetrics(SurrealDbClientSettings options, bool enabled) + { + throw new NotImplementedException(); + } + + protected override void SetTracing(SurrealDbClientSettings options, bool enabled) + { + throw new NotImplementedException(); + } + + protected override void TriggerActivity(SurrealDbClient service) + { + using var source = new CancellationTokenSource(100); + + service.Version(source.Token).Wait(); + } +} \ No newline at end of file diff --git a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/SurrealDbClientPublicApiTests.cs b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/SurrealDbClientPublicApiTests.cs new file mode 100644 index 00000000..15a8e7c1 --- /dev/null +++ b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/SurrealDbClientPublicApiTests.cs @@ -0,0 +1,87 @@ +// 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.Hosting; + +namespace CommunityToolkit.Aspire.SurrealDb.Tests; + +public class SurrealDbClientPublicApiTests +{ + [Fact] + public void AddSurrealClientShouldThrowWhenBuilderIsNull() + { + IHostApplicationBuilder builder = null!; + + const string connectionName = "surreal"; + + var action = () => builder.AddSurrealClient(connectionName); + + var exception = Assert.Throws(action); + Assert.Equal(nameof(builder), exception.ParamName); + } + + [Fact] + public void AddSurrealClientShouldThrowWhenNameIsNull() + { + var builder = Host.CreateEmptyApplicationBuilder(null); + + string connectionName = null!; + + var action = () => builder.AddSurrealClient(connectionName); + + var exception = Assert.Throws(action); + Assert.Equal(nameof(connectionName), exception.ParamName); + } + + [Fact] + public void AddSurrealClientShouldThrowWhenNameIsEmpty() + { + var builder = Host.CreateEmptyApplicationBuilder(null); + + string connectionName = ""; + + var action = () => builder.AddSurrealClient(connectionName); + + var exception = Assert.Throws(action); + Assert.Equal(nameof(connectionName), exception.ParamName); + } + + [Fact] + public void AddKeyedSurrealClientShouldThrowWhenBuilderIsNull() + { + IHostApplicationBuilder builder = null!; + + const string connectionName = "surreal"; + + var action = () => builder.AddKeyedSurrealClient(connectionName); + + var exception = Assert.Throws(action); + Assert.Equal(nameof(builder), exception.ParamName); + } + + [Fact] + public void AddKeyedSurrealClientShouldThrowWhenNameIsNull() + { + var builder = Host.CreateEmptyApplicationBuilder(null); + + string name = null!; + + var action = () => builder.AddKeyedSurrealClient(name); + + var exception = Assert.Throws(action); + Assert.Equal(nameof(name), exception.ParamName); + } + + [Fact] + public void AddKeyedSurrealClientShouldThrowWhenNameIsEmpty() + { + var builder = Host.CreateEmptyApplicationBuilder(null); + + string name = ""; + + var action = () => builder.AddKeyedSurrealClient(name); + + var exception = Assert.Throws(action); + Assert.Equal(nameof(name), exception.ParamName); + } +} \ No newline at end of file diff --git a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/SurrealDbContainerFixture.cs b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/SurrealDbContainerFixture.cs new file mode 100644 index 00000000..6a15775d --- /dev/null +++ b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/SurrealDbContainerFixture.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Components.Common.Tests; +using Aspire.Hosting; +using Aspire.Hosting.Utils; +using DotNet.Testcontainers.Builders; +using DotNet.Testcontainers.Containers; + +namespace CommunityToolkit.Aspire.SurrealDb.Tests; + +public sealed class SurrealDbContainerFixture : IAsyncLifetime +{ + private const string _username = "root"; + private string _password = string.Empty; + private const int _port = 8000; + + public IContainer? Container { get; private set; } + + public string GetConnectionString() + { + if (Container is null) + { + throw new InvalidOperationException("The test container was not initialized."); + } + + var endpoint = new UriBuilder("ws", Container.Hostname, Container.GetMappedPublicPort(_port), "/rpc").ToString(); + return $"Endpoint={endpoint};Username={_username};Password={_password}"; + } + + public async Task InitializeAsync() + { + if (RequiresDockerAttribute.IsSupported) + { + _password = PasswordGenerator.Generate(minLength: 8, lower: true, upper: true, numeric: true, special: false, minLower: 1, minUpper: 1, minNumeric: 1, minSpecial: 0); + + Container = new ContainerBuilder() + .WithImage($"{SurrealDbContainerImageTags.Registry}/{SurrealDbContainerImageTags.Image}:{SurrealDbContainerImageTags.Tag}") + .WithPortBinding(_port, true) + .WithWaitStrategy(Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(r => r.ForPort(_port))) + .WithEnvironment("SURREAL_USER", _username) + .WithEnvironment("SURREAL_PASS", _password) + .Build(); + + await Container.StartAsync(); + } + } + + public async Task DisposeAsync() + { + if (Container is not null) + { + await Container.DisposeAsync(); + } + } +} \ No newline at end of file From b8391bda44cb1a6250c8798e3ac4fdcc1bb23a69 Mon Sep 17 00:00:00 2001 From: Odonno Date: Fri, 15 Nov 2024 14:58:33 +0100 Subject: [PATCH 05/30] chore: set code owners for SurrealDB integration --- CODEOWNERS | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index b67fb7f0..a0e43348 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -21,6 +21,7 @@ /tests/CommunityToolkit.Aspire.Hosting.Azure.DataApiBuilder.Tests/ @tommasodotNET @FBoucher # CommunityToolkit.Aspire.Meilisearch + # CommunityToolkit.Aspire.Hosting.Meilisearch /examples/meilisearch/ @Alirexaa @@ -35,7 +36,6 @@ /src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/ @ErikEJ @jmezach /tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/ @ErikEJ @jmezach - # CommunityToolkit.Aspire.Hosting.Rust /examples/rust/ @Alirexaa @@ -43,6 +43,7 @@ /tests/CommunityToolkit.Aspire.Hosting.Rust/ @Alirexaa # CommunityToolkit.Aspire.EventStore + # CommunityToolkit.Aspire.Hosting.EventStore /examples/eventstore/ @fredimachado @@ -63,13 +64,14 @@ /src/CommunityToolkit.Aspire.Hosting.Ngrok/ @esskar /tests/CommunityToolkit.Aspire.Hosting.Ngrok.Tests/ @esskar -# CommunityToolkit.Aspire.Dapr.* +# CommunityToolkit.Aspire.Dapr.\* /examples/dapr**/ @FullStackChef @WhitWaldo @Paule96 /src/CommunityToolkit.Aspire.Hosting.Dapr**/ @FullStackChef @WhitWaldo @Paule96 -/tests/CommunityToolkit.Aspire.Hosting.Dapr**.Tests/ @FullStackChef @WhitWaldo @Paule96 +/tests/CommunityToolkit.Aspire.Hosting.Dapr\*\*.Tests/ @FullStackChef @WhitWaldo @Paule96 # CommunityToolkit.Aspire.RavenDB.Client + # CommunityToolkit.Aspire.Hosting.RavenDB /examples/ravendb/ @shiranshalom @@ -85,26 +87,32 @@ /examples/postgres-ext/ @Alirexaa # CommunityToolkit.Aspire.Hosting.DbGate + /src/CommunityToolkit.Aspire.Hosting.DbGate/ @Alirexaa /tests/CommunityToolkit.Aspire.Hosting.DbGate.Tests/ @Alirexaa /examples/dbgate/ @Alirexaa # CommunityToolkit.Aspire.Hosting.MongoDB.Extensions + /src/CommunityToolkit.Aspire.Hosting.MongoDB.Extensions/ @Alirexaa /tests/CommunityToolkit.Aspire.Hosting.MongoDB.Extensions.Tests/ @Alirexaa /examples/mongodb-ext/ @Alirexaa # CommunityToolkit.Aspire.Hosting.Redis.Extensions + /src/CommunityToolkit.Aspire.Hosting.Redis.Extensions/ @Alirexaa /tests/CommunityToolkit.Aspire.Hosting.Redis.Extensions.Tests/ @Alirexaa /examples/redis-ext/ @Alirexaa # CommunityToolkit.Aspire.Hosting.SqlServer.Extensions + /src/CommunityToolkit.Aspire.Hosting.SqlServer.Extensions/ @Alirexaa /tests/CommunityToolkit.Aspire.Hosting.SqlServer.Extensions.Tests/ @Alirexaa /examples/sqlserver-ext/ @Alirexaa +<<<<<<< HEAD # CommunityToolkit.Aspire.Minio.Client + # CommunityToolkit.Aspire.Hosting.Minio /examples/minio/ @Harold-Morgan @@ -112,4 +120,14 @@ /tests/CommunityToolkit.Aspire.Hosting.Minio.Tests/ @Harold-Morgan /src/CommunityToolkit.Aspire.Minio.Client/ @Harold-Morgan -/tests/CommunityToolkit.Aspire.Minio.Client.Tests/ @Harold-Morgan \ No newline at end of file +/tests/CommunityToolkit.Aspire.Minio.Client.Tests/ @Harold-Morgan + +# CommunityToolkit.Aspire.SurrealDb + +# CommunityToolkit.Aspire.Hosting.SurrealDb + +/examples/surrealdb/ @Odonno +/src/CommunityToolkit.Aspire.Hosting.SurrealDb/ @Odonno +/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/ @Odonno +/src/CommunityToolkit.Aspire.SurrealDb/ @Odonno +/tests/CommunityToolkit.Aspire.SurrealDb.Tests/ @Odonno From 6c08c2bd78c50cb89f6031b331490b352ec5e916 Mon Sep 17 00:00:00 2001 From: Odonno Date: Sat, 7 Dec 2024 11:17:06 +0100 Subject: [PATCH 06/30] feat: add Surrealist UI --- .../Properties/launchSettings.json | 4 +- .../Program.cs | 1 + .../Properties/launchSettings.json | 12 +- .../PublicAPI.Unshipped.txt | 3 + .../SurrealDbBuilderExtensions.cs | 143 +++++++++++++++++- .../SurrealDbContainerImageTags.cs | 12 +- .../SurrealistContainerResource.cs | 14 ++ 7 files changed, 179 insertions(+), 10 deletions(-) create mode 100644 src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealistContainerResource.cs diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Properties/launchSettings.json b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Properties/launchSettings.json index 5a2dd207..8ce06d4e 100644 --- a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Properties/launchSettings.json +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Properties/launchSettings.json @@ -5,7 +5,7 @@ "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "applicationUrl": "http://localhost:5284", + "applicationUrl": "http://localhost:5109", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -14,7 +14,7 @@ "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "applicationUrl": "https://localhost:7079;http://localhost:5284", + "applicationUrl": "https://localhost:7086;http://localhost:5109", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/Program.cs b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/Program.cs index 9c28ac8c..d83ecaca 100644 --- a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/Program.cs +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/Program.cs @@ -3,6 +3,7 @@ var builder = DistributedApplication.CreateBuilder(args); var db = builder.AddSurrealServer("surreal") + .WithSurrealist() .AddNamespace("ns") .AddDatabase("db"); diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/Properties/launchSettings.json b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/Properties/launchSettings.json index 000b800c..c65468bf 100644 --- a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/Properties/launchSettings.json +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/Properties/launchSettings.json @@ -5,24 +5,24 @@ "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "applicationUrl": "https://localhost:11918;http://localhost:37012", + "applicationUrl": "https://localhost:17138;http://localhost:15162", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "DOTNET_ENVIRONMENT": "Development", - "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:31745", - "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:12787" + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21257", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22098" } }, "http": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "applicationUrl": "http://localhost:15730", + "applicationUrl": "http://localhost:15162", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "DOTNET_ENVIRONMENT": "Development", - "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19870", - "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20522" + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19149", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20034" } } } diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/PublicAPI.Unshipped.txt b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/PublicAPI.Unshipped.txt index 16eb67b7..6c0d8e57 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/PublicAPI.Unshipped.txt +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/PublicAPI.Unshipped.txt @@ -18,9 +18,12 @@ Aspire.Hosting.ApplicationModel.SurrealDbServerResource.PasswordParameter.get -> Aspire.Hosting.ApplicationModel.SurrealDbServerResource.PrimaryEndpoint.get -> Aspire.Hosting.ApplicationModel.EndpointReference! Aspire.Hosting.ApplicationModel.SurrealDbServerResource.SurrealDbServerResource(string! name, Aspire.Hosting.ApplicationModel.ParameterResource? userName, Aspire.Hosting.ApplicationModel.ParameterResource! password) -> void Aspire.Hosting.ApplicationModel.SurrealDbServerResource.UserNameParameter.get -> Aspire.Hosting.ApplicationModel.ParameterResource? +Aspire.Hosting.ApplicationModel.SurrealistContainerResource +Aspire.Hosting.ApplicationModel.SurrealistContainerResource.SurrealistContainerResource(string! name) -> void Aspire.Hosting.SurrealDbBuilderExtensions static Aspire.Hosting.SurrealDbBuilderExtensions.AddDatabase(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string! name, string? databaseName = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! static Aspire.Hosting.SurrealDbBuilderExtensions.AddNamespace(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string! name, string? namespaceName = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! static Aspire.Hosting.SurrealDbBuilderExtensions.AddSurrealServer(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name, Aspire.Hosting.ApplicationModel.IResourceBuilder? userName = null, Aspire.Hosting.ApplicationModel.IResourceBuilder? password = null, int? port = null, bool strictMode = false) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! static Aspire.Hosting.SurrealDbBuilderExtensions.WithDataBindMount(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string! source) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! static Aspire.Hosting.SurrealDbBuilderExtensions.WithDataVolume(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string? name = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! +static Aspire.Hosting.SurrealDbBuilderExtensions.WithSurrealist(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, System.Action!>? configureContainer = null, string? containerName = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs index 35ff3e90..0b19573f 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Aspire.Hosting.ApplicationModel; @@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using SurrealDb.Net; +using System.Text.Json; namespace Aspire.Hosting; @@ -256,4 +257,144 @@ public static IResourceBuilder WithDataBindMount(this I return builder.WithBindMount(source, "/var/opt/surreal"); } + + /// + /// Adds a Surrealist UI instance for SurrealDB to the application model. + /// This version the package defaults to the 3.1.2 tag of the surrealdb/surrealist container image + /// + /// The SurrealDB server resource builder. + /// Callback to configure Surrealist container resource. + /// The name of the container (optional). + /// A reference to the . + public static IResourceBuilder WithSurrealist( + this IResourceBuilder builder, + Action>? configureContainer = null, + string? containerName = null + ) + where T : SurrealDbServerResource + { + ArgumentNullException.ThrowIfNull(builder); + + if (builder.ApplicationBuilder.Resources.OfType().SingleOrDefault() is { } existingSurrealistResource) + { + var builderForExistingResource = builder.ApplicationBuilder.CreateResourceBuilder(existingSurrealistResource); + configureContainer?.Invoke(builderForExistingResource); + + return builder; + } + + containerName ??= $"{builder.Resource.Name}-surrealist"; + + const string CONNECTIONS_FILE_PATH = "/usr/share/nginx/html/connections.json"; + + var surrealistContainer = new SurrealistContainerResource(containerName); + var surrealistContainerBuilder = builder.ApplicationBuilder.AddResource(surrealistContainer) + .WithImage(SurrealDbContainerImageTags.SurrealistImage, SurrealDbContainerImageTags.SurrealistTag) + .WithImageRegistry(SurrealDbContainerImageTags.SurrealistRegistry) + .WithHttpEndpoint(targetPort: 8080, name: "http") + .WithBindMount(Path.GetTempFileName(), CONNECTIONS_FILE_PATH) + .ExcludeFromManifest(); + + builder.ApplicationBuilder.Eventing.Subscribe( (e, ct) => + { + var serverFileMount = surrealistContainer.Annotations.OfType().Single(v => v.Target == CONNECTIONS_FILE_PATH); + var surrealDbServerResources = builder.ApplicationBuilder.Resources.OfType().ToList(); + + using var stream = new FileStream(serverFileMount.Source!, FileMode.Create); + using var writer = new Utf8JsonWriter(stream); + + // Need to grant read access to the config file on unix like systems. + if (!OperatingSystem.IsWindows()) + { + File.SetUnixFileMode(serverFileMount.Source!, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.GroupRead | UnixFileMode.OtherRead); + } + + writer.WriteStartObject(); + + if (surrealDbServerResources.Count == 1) + { + var uniqueSurrealDbResource = surrealDbServerResources[0]; + writer.WriteString("defaultConnection", uniqueSurrealDbResource.Name); + } + + writer.WriteStartArray("connections"); + + var surrealDbNamespaceResources = builder.ApplicationBuilder.Resources.OfType().ToList(); + var surrealDbDatabaseResources = builder.ApplicationBuilder.Resources.OfType().ToList(); + + foreach (var surrealInstance in surrealDbServerResources) + { + if (surrealInstance.PrimaryEndpoint.IsAllocated) + { + SurrealDbNamespaceResource? uniqueNamespace = null; + SurrealDbDatabaseResource? uniqueDatabase = null; + + var serverNamespaces = surrealDbNamespaceResources + .Where(ns => ns.Parent == surrealInstance) + .ToList(); + + if (serverNamespaces.Count == 1) + { + uniqueNamespace = serverNamespaces.First(); + + var nsDatabases = surrealDbDatabaseResources + .Where(db => db.Parent == uniqueNamespace) + .ToList(); + + if (nsDatabases.Count == 1) + { + uniqueDatabase = nsDatabases.First(); + } + } + + var endpoint = surrealInstance.PrimaryEndpoint; + + writer.WriteStartObject(); + + writer.WriteString("id", surrealInstance.Name); + writer.WriteString("name", surrealInstance.Name); + + if (uniqueNamespace is not null) + { + writer.WriteString("defaultNamespace", uniqueNamespace.NamespaceName); + } + if (uniqueDatabase is not null) + { + writer.WriteString("defaultDatabase", uniqueDatabase.DatabaseName); + } + + writer.WriteStartObject("authentication"); + writer.WriteString("protocol", "ws"); + // How to do host resolution? + writer.WriteString("hostname", $"localhost:{endpoint.Port}"); + writer.WriteString("mode", "root"); + if (uniqueNamespace is not null) + { + writer.WriteString("namespace", uniqueNamespace.NamespaceName); + } + if (uniqueDatabase is not null) + { + writer.WriteString("database", uniqueDatabase.DatabaseName); + } + + writer.WriteEndObject(); + + writer.WriteEndObject(); + } + } + + writer.WriteEndArray(); + + writer.WriteEndObject(); + + return Task.CompletedTask; + }); + + configureContainer?.Invoke(surrealistContainerBuilder); + + // TODO : missing + //surrealistContainerBuilder.WithRelationship(builder.Resource, "Surrealist"); + + return builder; + } } \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbContainerImageTags.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbContainerImageTags.cs index efb03611..a7fd3682 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbContainerImageTags.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbContainerImageTags.cs @@ -5,7 +5,17 @@ namespace Aspire.Hosting; internal sealed class SurrealDbContainerImageTags { + /// docker.io public const string Registry = "docker.io"; + /// surrealdb/surrealdb public const string Image = "surrealdb/surrealdb"; - public const string Tag = "v2.0.4"; + /// v2.1 + public const string Tag = "v2.1"; + + /// docker.io + public const string SurrealistRegistry = "docker.io"; + /// surrealdb/surrealist + public const string SurrealistImage = "surrealdb/surrealist"; + /// 3.1.6 + public const string SurrealistTag = "3.1.6"; } \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealistContainerResource.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealistContainerResource.cs new file mode 100644 index 00000000..76412425 --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealistContainerResource.cs @@ -0,0 +1,14 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace Aspire.Hosting.ApplicationModel; + +/// +/// Represents a container resource for Surrealist. +/// +/// The name of the container resource. +public sealed class SurrealistContainerResource(string name) : ContainerResource(ThrowIfNull(name)) +{ + private static string ThrowIfNull([NotNull] string? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null) + => argument ?? throw new ArgumentNullException(paramName); +} \ No newline at end of file From f82cce97cfcab38023463473141a4b2671b75f19 Mon Sep 17 00:00:00 2001 From: Odonno Date: Sat, 7 Dec 2024 11:24:35 +0100 Subject: [PATCH 07/30] fix: various fixes --- Directory.Packages.props | 17 +++++++++++++++-- ...t.Aspire.Hosting.SurrealDb.ApiService.csproj | 1 - ...lkit.Aspire.Hosting.SurrealDb.AppHost.csproj | 1 - ...ire.Hosting.SurrealDb.ServiceDefaults.csproj | 1 - .../SurrealDbBuilderExtensions.cs | 15 +++++++++------ .../SurrealDbServerResource.cs | 2 +- 6 files changed, 25 insertions(+), 12 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 57192622..605a1730 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -42,8 +42,6 @@ - - @@ -62,6 +60,17 @@ + + + + + + + + + + + @@ -98,6 +107,10 @@ + + + + diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService.csproj b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService.csproj index 2bf36ffe..2c32d9d4 100644 --- a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService.csproj +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService.csproj @@ -1,7 +1,6 @@  - net8.0 enable enable diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost.csproj b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost.csproj index d7535c8a..009a61e3 100644 --- a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost.csproj +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost.csproj @@ -3,7 +3,6 @@ Exe - net8.0 enable enable true diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ServiceDefaults/CommunityToolkit.Aspire.Hosting.SurrealDb.ServiceDefaults.csproj b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ServiceDefaults/CommunityToolkit.Aspire.Hosting.SurrealDb.ServiceDefaults.csproj index a546ce0b..c9a4399a 100644 --- a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ServiceDefaults/CommunityToolkit.Aspire.Hosting.SurrealDb.ServiceDefaults.csproj +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ServiceDefaults/CommunityToolkit.Aspire.Hosting.SurrealDb.ServiceDefaults.csproj @@ -1,7 +1,6 @@  - net8.0 enable enable true diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs index 0b19573f..611367f3 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Aspire.Hosting.ApplicationModel; @@ -16,11 +16,13 @@ namespace Aspire.Hosting; /// public static class SurrealDbBuilderExtensions { + private const int SurrealDbPort = 8000; private const string UserEnvVarName = "SURREAL_USER"; private const string PasswordEnvVarName = "SURREAL_PASS"; /// /// Adds a SurrealDB resource to the application model. A container is used for local development. + /// The default image is and the tag is . /// /// The . /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. @@ -71,7 +73,7 @@ public static IResourceBuilder AddSurrealServer( var surrealServer = new SurrealDbServerResource(name, userName?.Resource, passwordParameter); return builder.AddResource(surrealServer) - .WithEndpoint(port: port, targetPort: 8000, name: SurrealDbServerResource.PrimaryEndpointName) + .WithEndpoint(port: port, targetPort: SurrealDbPort, name: SurrealDbServerResource.PrimaryEndpointName) .WithImage(SurrealDbContainerImageTags.Image, SurrealDbContainerImageTags.Tag) .WithImageRegistry(SurrealDbContainerImageTags.Registry) .WithEnvironment(context => @@ -166,12 +168,13 @@ public static IResourceBuilder AddDatabase( builder.ApplicationBuilder.Eventing.Subscribe(surrealServerDatabase, async (@event, ct) => { var connectionString = await surrealServerDatabase.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false); - if (connectionString == null) + if (connectionString is null) { throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{surrealServerDatabase}' resource but the connection string was null."); } - surrealDbClient = new SurrealDbClient(connectionString); + var options = new SurrealDbOptionsBuilder().FromConnectionString(connectionString).Build(); + surrealDbClient = new SurrealDbClient(options); }); string namespaceName = builder.Resource.Name; @@ -199,7 +202,7 @@ public static IResourceBuilder AddDatabase( /// The . /// /// - /// Add an Meilisearch container to the application model and reference it in a .NET project. + /// Add a SurrealDB container to the application model and reference it in a .NET project. /// Additionally, in this example a data volume is added to the container /// to allow data to be persisted across container restarts. /// @@ -260,7 +263,7 @@ public static IResourceBuilder WithDataBindMount(this I /// /// Adds a Surrealist UI instance for SurrealDB to the application model. - /// This version the package defaults to the 3.1.2 tag of the surrealdb/surrealist container image + /// The default image is and the tag is . /// /// The SurrealDB server resource builder. /// Callback to configure Surrealist container resource. diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbServerResource.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbServerResource.cs index eae7f334..a351fb0a 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.IPV4Host)}:{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. From bbb4952bc8e72c08e2e1902a2d05f60ff53b096e Mon Sep 17 00:00:00 2001 From: Odonno Date: Tue, 10 Dec 2024 22:37:00 +0100 Subject: [PATCH 08/30] test: fix tests --- .../PublicAPI.Unshipped.txt | 1 + .../SurrealDbServerResource.cs | 13 ++++++++++--- .../AddSurrealServerTests.cs | 4 ++-- .../AppHostTests.cs | 12 ++++++++---- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/PublicAPI.Unshipped.txt b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/PublicAPI.Unshipped.txt index 6c0d8e57..efe8579b 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/PublicAPI.Unshipped.txt +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/PublicAPI.Unshipped.txt @@ -16,6 +16,7 @@ Aspire.Hosting.ApplicationModel.SurrealDbServerResource.GetConnectionStringAsync Aspire.Hosting.ApplicationModel.SurrealDbServerResource.Namespaces.get -> System.Collections.Generic.IReadOnlyDictionary! Aspire.Hosting.ApplicationModel.SurrealDbServerResource.PasswordParameter.get -> Aspire.Hosting.ApplicationModel.ParameterResource! Aspire.Hosting.ApplicationModel.SurrealDbServerResource.PrimaryEndpoint.get -> Aspire.Hosting.ApplicationModel.EndpointReference! +Aspire.Hosting.ApplicationModel.SurrealDbServerResource.SecondaryEndpoint.get -> Aspire.Hosting.ApplicationModel.EndpointReference! Aspire.Hosting.ApplicationModel.SurrealDbServerResource.SurrealDbServerResource(string! name, Aspire.Hosting.ApplicationModel.ParameterResource? userName, Aspire.Hosting.ApplicationModel.ParameterResource! password) -> void Aspire.Hosting.ApplicationModel.SurrealDbServerResource.UserNameParameter.get -> Aspire.Hosting.ApplicationModel.ParameterResource? Aspire.Hosting.ApplicationModel.SurrealistContainerResource diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbServerResource.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbServerResource.cs index a351fb0a..32df991a 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbServerResource.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbServerResource.cs @@ -8,10 +8,11 @@ namespace Aspire.Hosting.ApplicationModel; /// public class SurrealDbServerResource : ContainerResource, IResourceWithConnectionString { - internal const string PrimaryEndpointName = "tcp"; + internal const string PrimaryEndpointName = "ws"; + internal const string SecondaryEndpointName = "http"; private const string DefaultUserName = "root"; - private const string SchemeUri = "ws"; + private const string DefaultScheme = PrimaryEndpointName; /// /// Initializes a new instance of the class. @@ -28,6 +29,7 @@ ParameterResource password ArgumentNullException.ThrowIfNull(password); PrimaryEndpoint = new(this, PrimaryEndpointName); + SecondaryEndpoint = new(this, SecondaryEndpointName); UserNameParameter = userName; PasswordParameter = password; } @@ -37,6 +39,11 @@ ParameterResource password /// public EndpointReference PrimaryEndpoint { get; } + /// + /// Gets the secondary endpoint for the SurrealDB instance. + /// + public EndpointReference SecondaryEndpoint { get; } + /// /// Gets the parameter that contains the SurrealDB username. /// @@ -54,7 +61,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={DefaultScheme}://{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 52175523..e07e40dc 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AddSurrealServerTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AddSurrealServerTests.cs @@ -91,7 +91,7 @@ public async Task SurrealServerCreatesConnectionString() var connectionStringResource = Assert.Single(appModel.Resources.OfType()); var connectionString = await connectionStringResource.GetConnectionStringAsync(default); - Assert.Equal("Server=ws://127.0.0.1:8000/rpc;User=root;Password=p@ssw0rd1", connectionString); + 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); } @@ -116,7 +116,7 @@ public async Task SurrealServerDatabaseCreatesConnectionString() var connectionStringResource = (IResourceWithConnectionString)surrealResource; var connectionString = await connectionStringResource.GetConnectionStringAsync(); - Assert.Equal("Server=ws://127.0.0.1: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); } diff --git a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AppHostTests.cs b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AppHostTests.cs index 16936db8..2b98ab6e 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AppHostTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AppHostTests.cs @@ -14,9 +14,13 @@ public class AppHostTests(AspireIntegrationTestFixture Date: Tue, 10 Dec 2024 22:39:39 +0100 Subject: [PATCH 09/30] refactor: use AddTypeActivatedCheck --- .../SurrealDbBuilderExtensions.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs index 611367f3..cbc609d9 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs @@ -182,13 +182,7 @@ public static IResourceBuilder AddDatabase( string healthCheckKey = $"{serverName}_{namespaceName}_{name}_check"; builder.ApplicationBuilder.Services.AddHealthChecks() - .Add(new HealthCheckRegistration( - healthCheckKey, - sp => new SurrealDbHealthCheck(surrealDbClient!), - failureStatus: default, - tags: default, - timeout: default) - ); + .AddTypeActivatedCheck(healthCheckKey, surrealDbClient!); return builder.ApplicationBuilder.AddResource(surrealServerDatabase) .WithHealthCheck(healthCheckKey); From dff10460f831b355c19034ef05e0a622f1bf499a Mon Sep 17 00:00:00 2001 From: Odonno Date: Tue, 10 Dec 2024 22:58:53 +0100 Subject: [PATCH 10/30] revert: revert change on tcp endpoint --- .../PublicAPI.Unshipped.txt | 1 - .../SurrealDbBuilderExtensions.cs | 8 +++++++- .../SurrealDbServerResource.cs | 13 +++---------- .../AppHostTests.cs | 2 +- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/PublicAPI.Unshipped.txt b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/PublicAPI.Unshipped.txt index efe8579b..6c0d8e57 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/PublicAPI.Unshipped.txt +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/PublicAPI.Unshipped.txt @@ -16,7 +16,6 @@ Aspire.Hosting.ApplicationModel.SurrealDbServerResource.GetConnectionStringAsync Aspire.Hosting.ApplicationModel.SurrealDbServerResource.Namespaces.get -> System.Collections.Generic.IReadOnlyDictionary! Aspire.Hosting.ApplicationModel.SurrealDbServerResource.PasswordParameter.get -> Aspire.Hosting.ApplicationModel.ParameterResource! Aspire.Hosting.ApplicationModel.SurrealDbServerResource.PrimaryEndpoint.get -> Aspire.Hosting.ApplicationModel.EndpointReference! -Aspire.Hosting.ApplicationModel.SurrealDbServerResource.SecondaryEndpoint.get -> Aspire.Hosting.ApplicationModel.EndpointReference! Aspire.Hosting.ApplicationModel.SurrealDbServerResource.SurrealDbServerResource(string! name, Aspire.Hosting.ApplicationModel.ParameterResource? userName, Aspire.Hosting.ApplicationModel.ParameterResource! password) -> void Aspire.Hosting.ApplicationModel.SurrealDbServerResource.UserNameParameter.get -> Aspire.Hosting.ApplicationModel.ParameterResource? Aspire.Hosting.ApplicationModel.SurrealistContainerResource diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs index cbc609d9..611367f3 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs @@ -182,7 +182,13 @@ public static IResourceBuilder AddDatabase( string healthCheckKey = $"{serverName}_{namespaceName}_{name}_check"; builder.ApplicationBuilder.Services.AddHealthChecks() - .AddTypeActivatedCheck(healthCheckKey, surrealDbClient!); + .Add(new HealthCheckRegistration( + healthCheckKey, + sp => new SurrealDbHealthCheck(surrealDbClient!), + failureStatus: default, + tags: default, + timeout: default) + ); return builder.ApplicationBuilder.AddResource(surrealServerDatabase) .WithHealthCheck(healthCheckKey); diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbServerResource.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbServerResource.cs index 32df991a..a351fb0a 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbServerResource.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbServerResource.cs @@ -8,11 +8,10 @@ namespace Aspire.Hosting.ApplicationModel; /// public class SurrealDbServerResource : ContainerResource, IResourceWithConnectionString { - internal const string PrimaryEndpointName = "ws"; - internal const string SecondaryEndpointName = "http"; + internal const string PrimaryEndpointName = "tcp"; private const string DefaultUserName = "root"; - private const string DefaultScheme = PrimaryEndpointName; + private const string SchemeUri = "ws"; /// /// Initializes a new instance of the class. @@ -29,7 +28,6 @@ ParameterResource password ArgumentNullException.ThrowIfNull(password); PrimaryEndpoint = new(this, PrimaryEndpointName); - SecondaryEndpoint = new(this, SecondaryEndpointName); UserNameParameter = userName; PasswordParameter = password; } @@ -39,11 +37,6 @@ ParameterResource password /// public EndpointReference PrimaryEndpoint { get; } - /// - /// Gets the secondary endpoint for the SurrealDB instance. - /// - public EndpointReference SecondaryEndpoint { get; } - /// /// Gets the parameter that contains the SurrealDB username. /// @@ -61,7 +54,7 @@ UserNameParameter is not null ? private ReferenceExpression ConnectionString => ReferenceExpression.Create( - $"Server={DefaultScheme}://{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/AppHostTests.cs b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AppHostTests.cs index 2b98ab6e..721cd7d3 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AppHostTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AppHostTests.cs @@ -17,7 +17,7 @@ public async Task SurrealResourceStartsAndRespondsOk() const string resourceName = "surreal"; await fixture.ResourceNotificationService.WaitForResourceHealthyAsync(resourceName).WaitAsync(TimeSpan.FromMinutes(1)); - var tcpUri = fixture.GetEndpoint(resourceName, "ws"); + var tcpUri = fixture.GetEndpoint(resourceName, "tcp"); var baseUri = new Uri(tcpUri.AbsoluteUri.Replace("tcp://", "http://")); var httpClient = new HttpClient(); httpClient.BaseAddress = baseUri; From 93017a04ff937c7fb8e726b03663ed321544b682 Mon Sep 17 00:00:00 2001 From: Odonno Date: Wed, 11 Dec 2024 18:31:30 +0100 Subject: [PATCH 11/30] feat: add ability to set `path` options --- .../PublicAPI.Unshipped.txt | 2 +- .../SurrealDbBuilderExtensions.cs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/PublicAPI.Unshipped.txt b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/PublicAPI.Unshipped.txt index 6c0d8e57..1f93c2b5 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/PublicAPI.Unshipped.txt +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/PublicAPI.Unshipped.txt @@ -23,7 +23,7 @@ Aspire.Hosting.ApplicationModel.SurrealistContainerResource.SurrealistContainerR Aspire.Hosting.SurrealDbBuilderExtensions static Aspire.Hosting.SurrealDbBuilderExtensions.AddDatabase(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string! name, string? databaseName = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! static Aspire.Hosting.SurrealDbBuilderExtensions.AddNamespace(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string! name, string? namespaceName = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! -static Aspire.Hosting.SurrealDbBuilderExtensions.AddSurrealServer(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name, Aspire.Hosting.ApplicationModel.IResourceBuilder? userName = null, Aspire.Hosting.ApplicationModel.IResourceBuilder? password = null, int? port = null, bool strictMode = false) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! +static Aspire.Hosting.SurrealDbBuilderExtensions.AddSurrealServer(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name, Aspire.Hosting.ApplicationModel.IResourceBuilder? userName = null, Aspire.Hosting.ApplicationModel.IResourceBuilder? password = null, int? port = null, string! path = "memory", bool strictMode = false) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! static Aspire.Hosting.SurrealDbBuilderExtensions.WithDataBindMount(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string! source) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! static Aspire.Hosting.SurrealDbBuilderExtensions.WithDataVolume(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string? name = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! static Aspire.Hosting.SurrealDbBuilderExtensions.WithSurrealist(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, System.Action!>? configureContainer = null, string? containerName = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs index 611367f3..da6efc04 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs @@ -29,6 +29,7 @@ public static class SurrealDbBuilderExtensions /// The parameter used to provide the administrator username for the SurrealDB resource. /// The parameter used to provide the administrator password for the SurrealDB resource. If a random password will be generated. /// The host port for the SurrealDB instance. + /// Sets the path for storing data. If no argument is given, the default of memory for non-persistent storage in memory is assumed. /// Whether strict mode is enabled on the server. /// A reference to the . /// @@ -53,6 +54,7 @@ public static IResourceBuilder AddSurrealServer( IResourceBuilder? userName = null, IResourceBuilder? password = null, int? port = null, + string path = "memory", bool strictMode = false ) { @@ -61,7 +63,8 @@ public static IResourceBuilder AddSurrealServer( var args = new List { - "start" + "start", + path }; if (strictMode) { From 9381c9a6a518015435c02d602326a16a9b5b90b3 Mon Sep 17 00:00:00 2001 From: Odonno Date: Thu, 12 Dec 2024 18:03:58 +0100 Subject: [PATCH 12/30] chore: update to SurrealDb.Net v0.7.0 --- Directory.Packages.props | 1 + .../Program.cs | 3 +-- .../AspireSurrealDbExtensions.cs | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 605a1730..8348706d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -109,6 +109,7 @@ + diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Program.cs b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Program.cs index 0f2ce474..0f13ace2 100644 --- a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Program.cs +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Program.cs @@ -7,8 +7,7 @@ builder.AddSurrealClient("db", settings => { - // TODO : Wait for v0.7 - // settings.Options!.NamingPolicy = "CamelCase"; + settings.Options!.NamingPolicy = "CamelCase"; }); var app = builder.Build(); diff --git a/src/CommunityToolkit.Aspire.SurrealDb/AspireSurrealDbExtensions.cs b/src/CommunityToolkit.Aspire.SurrealDb/AspireSurrealDbExtensions.cs index b91515cd..d3ba822d 100644 --- a/src/CommunityToolkit.Aspire.SurrealDb/AspireSurrealDbExtensions.cs +++ b/src/CommunityToolkit.Aspire.SurrealDb/AspireSurrealDbExtensions.cs @@ -86,8 +86,7 @@ private static void AddSurrealClient( } else { - // TODO : Implement AddKeyedSurreal - //builder.Services.AddKeyedSurreal(serviceKey, settings.Options, settings.Lifetime); + builder.Services.AddKeyedSurreal(serviceKey, settings.Options, settings.Lifetime); } if (!settings.DisableHealthChecks) From 115f440bd41c3ec4a55e6aa4c3c9e2b3a943f484 Mon Sep 17 00:00:00 2001 From: Odonno Date: Fri, 3 Jan 2025 13:19:37 +0100 Subject: [PATCH 13/30] fix: add missing package reference --- .../CommunityToolkit.Aspire.Hosting.SurrealDb.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/CommunityToolkit.Aspire.Hosting.SurrealDb.csproj b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/CommunityToolkit.Aspire.Hosting.SurrealDb.csproj index bbaaf2e1..44848604 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/CommunityToolkit.Aspire.Hosting.SurrealDb.csproj +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/CommunityToolkit.Aspire.Hosting.SurrealDb.csproj @@ -11,6 +11,7 @@ + From 5ee5cfa1f46d9ff0456f8da8182631a64efc62f3 Mon Sep 17 00:00:00 2001 From: Odonno Date: Fri, 3 Jan 2025 13:28:39 +0100 Subject: [PATCH 14/30] refactor: replace PasswordGenerator with GenerateParameterDefault --- ...CommunityToolkit.Aspire.SurrealDb.Tests.csproj | 1 - .../SurrealDbContainerFixture.cs | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/CommunityToolkit.Aspire.SurrealDb.Tests.csproj b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/CommunityToolkit.Aspire.SurrealDb.Tests.csproj index c5746083..4fc1af1c 100644 --- a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/CommunityToolkit.Aspire.SurrealDb.Tests.csproj +++ b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/CommunityToolkit.Aspire.SurrealDb.Tests.csproj @@ -14,7 +14,6 @@ - diff --git a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/SurrealDbContainerFixture.cs b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/SurrealDbContainerFixture.cs index 6a15775d..3f69e630 100644 --- a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/SurrealDbContainerFixture.cs +++ b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/SurrealDbContainerFixture.cs @@ -32,7 +32,20 @@ public async Task InitializeAsync() { if (RequiresDockerAttribute.IsSupported) { - _password = PasswordGenerator.Generate(minLength: 8, lower: true, upper: true, numeric: true, special: false, minLower: 1, minUpper: 1, minNumeric: 1, minSpecial: 0); + var paramGenerator = new GenerateParameterDefault + { + MinLength = 8, + Lower = true, + Upper = true, + Numeric = true, + Special = false, + MinLower = 1, + MinUpper = 1, + MinNumeric = 1, + MinSpecial = 0 + }; + + _password = paramGenerator.GetDefaultValue(); Container = new ContainerBuilder() .WithImage($"{SurrealDbContainerImageTags.Registry}/{SurrealDbContainerImageTags.Image}:{SurrealDbContainerImageTags.Tag}") From 8a4cbd7dff7249e72d6dde83b8b95a49841fd938 Mon Sep 17 00:00:00 2001 From: Odonno Date: Wed, 8 Jan 2025 13:27:35 +0100 Subject: [PATCH 15/30] fix: apply command to SurrealDbContainerFixture --- .../SurrealDbContainerFixture.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/SurrealDbContainerFixture.cs b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/SurrealDbContainerFixture.cs index 3f69e630..560d17e7 100644 --- a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/SurrealDbContainerFixture.cs +++ b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/SurrealDbContainerFixture.cs @@ -53,6 +53,7 @@ public async Task InitializeAsync() .WithWaitStrategy(Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(r => r.ForPort(_port))) .WithEnvironment("SURREAL_USER", _username) .WithEnvironment("SURREAL_PASS", _password) + .WithCommand("start", "memory") .Build(); await Container.StartAsync(); From 81220b4761387140f80126bc96008e26389f9a1c Mon Sep 17 00:00:00 2001 From: Odonno Date: Wed, 8 Jan 2025 13:40:30 +0100 Subject: [PATCH 16/30] test: default connection string should have a key --- .../CommunityToolkit.Aspire.SurrealDb.Tests/ConformanceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/ConformanceTests.cs b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/ConformanceTests.cs index b237d303..c0d49f47 100644 --- a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/ConformanceTests.cs +++ b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/ConformanceTests.cs @@ -39,7 +39,7 @@ protected override void PopulateConfiguration(ConfigurationManager configuration configuration.AddInMemoryCollection( [ new KeyValuePair(CreateConfigKey("Aspire:Surreal:Client", key, "Endpoint"), GetConnectionStringKeyValue(connectionString,"Endpoint")), - new KeyValuePair($"ConnectionStrings:{key}", $"{connectionString}") + new KeyValuePair($"ConnectionStrings:{key ?? "surreal"}", $"{connectionString}") ]); } From df751c2612cfbfc873bc87802fe18a1a897c2eeb Mon Sep 17 00:00:00 2001 From: Odonno Date: Sat, 1 Feb 2025 12:37:18 +0100 Subject: [PATCH 17/30] test: fix tests --- .../AppHostTests.cs | 12 +++++----- .../SurrealDbFunctionalTests.cs | 6 ++--- .../AspireSurrealClientExtensionsTest.cs | 3 +-- .../ConfigurationTests.cs | 22 ++++++------------- 4 files changed, 17 insertions(+), 26 deletions(-) diff --git a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AppHostTests.cs b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AppHostTests.cs index 721cd7d3..2861ae22 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AppHostTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AppHostTests.cs @@ -3,7 +3,6 @@ using CommunityToolkit.Aspire.Testing; using Aspire.Components.Common.Tests; -using FluentAssertions; using System.Net.Http.Json; namespace CommunityToolkit.Aspire.Hosting.SurrealDb.Tests; @@ -24,7 +23,7 @@ public async Task SurrealResourceStartsAndRespondsOk() var response = await httpClient.GetAsync("/"); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); } [Fact] @@ -35,16 +34,17 @@ public async Task ApiServiceStartsAndRespondsOk() var httpClient = fixture.CreateHttpClient(resourceName); var todoResponse = await httpClient.GetAsync("/api/todo"); - todoResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, todoResponse.StatusCode); var initResponse = await httpClient.PostAsync("/init", null); - initResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, initResponse.StatusCode); var weatherForecastResponse = await httpClient.GetAsync("/api/weatherForecast"); - weatherForecastResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, weatherForecastResponse.StatusCode); var data = await weatherForecastResponse.Content.ReadFromJsonAsync>(); - data.Should().NotBeNullOrEmpty(); + Assert.NotNull(data); + Assert.NotEmpty(data); } } \ No newline at end of file diff --git a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbFunctionalTests.cs b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbFunctionalTests.cs index 576a157e..d798769b 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbFunctionalTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbFunctionalTests.cs @@ -6,7 +6,6 @@ using Aspire.Hosting.Utils; using Bogus; using CommunityToolkit.Aspire.Testing; -using FluentAssertions; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Hosting; using SurrealDb.Net; @@ -250,10 +249,11 @@ private static async Task CreateTestData(SurrealDbClient surrealDbClient) private static async Task AssertTestData(SurrealDbClient surrealDbClient) { var records = await surrealDbClient.Select(Todo.Table); - records.Count().Should().Be(_generatedTodoCount); + Assert.Equal(_generatedTodoCount, records.Count()); var firstRecord = await surrealDbClient.Select((Todo.Table, "1")); - firstRecord.Should().BeEquivalentTo(_todoList[0]); + Assert.NotNull(firstRecord); + Assert.Equivalent(firstRecord, _todoList[0]); } private sealed class Todo : SurrealRecord diff --git a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/AspireSurrealClientExtensionsTest.cs b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/AspireSurrealClientExtensionsTest.cs index e179b532..ae23da79 100644 --- a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/AspireSurrealClientExtensionsTest.cs +++ b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/AspireSurrealClientExtensionsTest.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using Aspire.Components.Common.Tests; -using FluentAssertions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Hosting; @@ -79,7 +78,7 @@ public void AddSurrealClient_HealthCheckShouldNotBeRegisteredWhenDisabled(bool u var healthCheckService = host.Services.GetService(); - healthCheckService.Should().BeNull(); + Assert.Null(healthCheckService); } [Fact] diff --git a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/ConfigurationTests.cs b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/ConfigurationTests.cs index 08437b71..162d09f9 100644 --- a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/ConfigurationTests.cs +++ b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/ConfigurationTests.cs @@ -1,27 +1,19 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using FluentAssertions; - namespace CommunityToolkit.Aspire.SurrealDb.Tests; public class ConfigurationTests { [Fact] - public void HealthChecksEnabledByDefault() - { - new SurrealDbClientSettings().DisableHealthChecks.Should().BeFalse(); - } + public void HealthChecksEnabledByDefault() => + Assert.False(new SurrealDbClientSettings().DisableHealthChecks); [Fact] - public void OptionsAreNullByDefault() - { - new SurrealDbClientSettings().Options.Should().BeNull(); - } - + public void OptionsAreNullByDefault() => + Assert.Null(new SurrealDbClientSettings().Options); + [Fact] - public void LifetimeIsSingletonByDefault() - { - new SurrealDbClientSettings().Lifetime.Should().Be(ServiceLifetime.Singleton); - } + public void LifetimeIsSingletonByDefault() => + Assert.Equal(ServiceLifetime.Singleton, new SurrealDbClientSettings().Lifetime); } From 7fd3fb1c1ac5df805b50995571c8b4963e296c08 Mon Sep 17 00:00:00 2001 From: Odonno Date: Sat, 29 Mar 2025 09:59:10 +0100 Subject: [PATCH 18/30] chore: upgrade to latest versions --- Directory.Packages.props | 8 ++++---- .../SurrealDbContainerImageTags.cs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 8348706d..eb94c8f3 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -20,7 +20,6 @@ - @@ -93,12 +92,14 @@ + + @@ -107,9 +108,8 @@ - - - + + diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbContainerImageTags.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbContainerImageTags.cs index a7fd3682..b605b584 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbContainerImageTags.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbContainerImageTags.cs @@ -9,13 +9,13 @@ internal sealed class SurrealDbContainerImageTags public const string Registry = "docker.io"; /// surrealdb/surrealdb public const string Image = "surrealdb/surrealdb"; - /// v2.1 - public const string Tag = "v2.1"; + /// v2.2 + public const string Tag = "v2.2"; /// docker.io public const string SurrealistRegistry = "docker.io"; /// surrealdb/surrealist public const string SurrealistImage = "surrealdb/surrealist"; - /// 3.1.6 - public const string SurrealistTag = "3.1.6"; + /// 3.3.0 + public const string SurrealistTag = "3.3.0"; } \ No newline at end of file From 4c77ea55e79763a4214e0036ad0ba0e8006c05f4 Mon Sep 17 00:00:00 2001 From: Odonno Date: Sat, 29 Mar 2025 10:02:43 +0100 Subject: [PATCH 19/30] feat: add relationship for surrealist UI resource --- .../SurrealDbBuilderExtensions.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs index da6efc04..04651177 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs @@ -299,6 +299,7 @@ public static IResourceBuilder WithSurrealist( .WithImageRegistry(SurrealDbContainerImageTags.SurrealistRegistry) .WithHttpEndpoint(targetPort: 8080, name: "http") .WithBindMount(Path.GetTempFileName(), CONNECTIONS_FILE_PATH) + .WithRelationship(builder.Resource, "Surrealist") .ExcludeFromManifest(); builder.ApplicationBuilder.Eventing.Subscribe( (e, ct) => @@ -398,9 +399,6 @@ public static IResourceBuilder WithSurrealist( configureContainer?.Invoke(surrealistContainerBuilder); - // TODO : missing - //surrealistContainerBuilder.WithRelationship(builder.Resource, "Surrealist"); - return builder; } } \ No newline at end of file From ab2ef56fb2255201f9cd639daac42803bc538f38 Mon Sep 17 00:00:00 2001 From: Odonno Date: Sat, 29 Mar 2025 10:05:20 +0100 Subject: [PATCH 20/30] ci: add test projects to the list of test projects --- .github/workflows/tests.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index cfb4a519..3c9c51c2 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -53,6 +53,7 @@ jobs: Hosting.Sqlite.Tests, Hosting.k6.Tests, Hosting.Minio.Tests, + Hosting.SurrealDb.Tests, # Client integration tests EventStore.Tests, @@ -64,6 +65,7 @@ jobs: OllamaSharp.Tests, RavenDB.Client.Tests, Minio.Client.Tests, + SurrealDb.Tests, ] steps: From 6a2b0a3cdc8d14bc051493a503c71e48660ba144 Mon Sep 17 00:00:00 2001 From: Odonno Date: Sat, 29 Mar 2025 10:18:39 +0100 Subject: [PATCH 21/30] fix: remove duplicates nuget package versions --- Directory.Packages.props | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index eb94c8f3..29ab12a0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -59,16 +59,6 @@ - - - - - - - - - - From 8c78c4dcc7bef7cabcac81f01aca972adc172339 Mon Sep 17 00:00:00 2001 From: Odonno Date: Sat, 29 Mar 2025 11:08:55 +0100 Subject: [PATCH 22/30] fix: fix build --- .../CommunityToolkit.Aspire.Hosting.SurrealDb.csproj | 1 - .../SurrealDbBuilderExtensions.cs | 4 +--- .../CommunityToolkit.Aspire.Hosting.SurrealDb.Tests.csproj | 2 -- .../SurrealDbFunctionalTests.cs | 4 +--- 4 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/CommunityToolkit.Aspire.Hosting.SurrealDb.csproj b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/CommunityToolkit.Aspire.Hosting.SurrealDb.csproj index 44848604..0b64790c 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/CommunityToolkit.Aspire.Hosting.SurrealDb.csproj +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/CommunityToolkit.Aspire.Hosting.SurrealDb.csproj @@ -6,7 +6,6 @@ - diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs index 04651177..981d2248 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs @@ -226,9 +226,7 @@ public static IResourceBuilder WithDataVolume(this IRes { ArgumentNullException.ThrowIfNull(builder); -#pragma warning disable CTASPIRE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - return builder.WithVolume(name ?? VolumeNameGenerator.CreateVolumeName(builder, "data"), "/var/opt/surreal"); -#pragma warning restore CTASPIRE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + return builder.WithVolume(name ?? VolumeNameGenerator.Generate(builder, "data"), "/var/opt/surreal"); } /// diff --git a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests.csproj b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests.csproj index b269b0b4..43016c58 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests.csproj +++ b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests.csproj @@ -6,8 +6,6 @@ - - diff --git a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbFunctionalTests.cs b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbFunctionalTests.cs index d798769b..db24c66d 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbFunctionalTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbFunctionalTests.cs @@ -87,9 +87,7 @@ public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume) if (useVolume) { // Use a deterministic volume name to prevent them from exhausting the machines if deletion fails -#pragma warning disable CTASPIRE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - volumeName = VolumeNameGenerator.CreateVolumeName(surrealServer1, nameof(WithDataShouldPersistStateBetweenUsages)); -#pragma warning restore CTASPIRE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + volumeName = VolumeNameGenerator.Generate(surrealServer1, nameof(WithDataShouldPersistStateBetweenUsages)); // if the volume already exists (because of a crashing previous run), delete it DockerUtils.AttemptDeleteDockerVolume(volumeName, throwOnFailure: true); From 8c974656807d338160fb2c86e22cc55061039ca7 Mon Sep 17 00:00:00 2001 From: Odonno Date: Sat, 29 Mar 2025 12:32:49 +0100 Subject: [PATCH 23/30] test: fix some tests --- Directory.Packages.props | 3 ++- .../CommunityToolkit.Aspire.Hosting.SurrealDb.csproj | 1 + .../SurrealDbBuilderExtensions.cs | 9 +-------- .../AspireSurrealDbExtensions.cs | 4 +++- .../SurrealDbFunctionalTests.cs | 4 ++-- .../AspireSurrealClientExtensionsTest.cs | 6 +++--- 6 files changed, 12 insertions(+), 15 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 29ab12a0..96335190 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -23,8 +23,10 @@ + + @@ -80,7 +82,6 @@ - diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/CommunityToolkit.Aspire.Hosting.SurrealDb.csproj b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/CommunityToolkit.Aspire.Hosting.SurrealDb.csproj index 0b64790c..fb6f6c6f 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/CommunityToolkit.Aspire.Hosting.SurrealDb.csproj +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/CommunityToolkit.Aspire.Hosting.SurrealDb.csproj @@ -11,6 +11,7 @@ + diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs index 981d2248..2bd85ba4 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs @@ -184,14 +184,7 @@ public static IResourceBuilder AddDatabase( string serverName = builder.Resource.Parent.Name; string healthCheckKey = $"{serverName}_{namespaceName}_{name}_check"; - builder.ApplicationBuilder.Services.AddHealthChecks() - .Add(new HealthCheckRegistration( - healthCheckKey, - sp => new SurrealDbHealthCheck(surrealDbClient!), - failureStatus: default, - tags: default, - timeout: default) - ); + builder.ApplicationBuilder.Services.AddHealthChecks().AddSurreal(_ => surrealDbClient!, healthCheckKey); return builder.ApplicationBuilder.AddResource(surrealServerDatabase) .WithHealthCheck(healthCheckKey); diff --git a/src/CommunityToolkit.Aspire.SurrealDb/AspireSurrealDbExtensions.cs b/src/CommunityToolkit.Aspire.SurrealDb/AspireSurrealDbExtensions.cs index d3ba822d..c1821ae2 100644 --- a/src/CommunityToolkit.Aspire.SurrealDb/AspireSurrealDbExtensions.cs +++ b/src/CommunityToolkit.Aspire.SurrealDb/AspireSurrealDbExtensions.cs @@ -77,7 +77,9 @@ private static void AddSurrealClient( if (settings.Options is null) { - throw new NullReferenceException("SurrealDB configured options cannot be null."); + // Ensures that when the connection information is missing, an exception isn't thrown before the host + // is built, so any exception can be logged with ILogger. + return; } if (serviceKey is null) diff --git a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbFunctionalTests.cs b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbFunctionalTests.cs index db24c66d..a9003378 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbFunctionalTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbFunctionalTests.cs @@ -78,7 +78,7 @@ public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume) { using var builder1 = TestDistributedApplicationBuilder.Create(testOutputHelper); - var surrealServer1 = builder1.AddSurrealServer("surreal"); + var surrealServer1 = builder1.AddSurrealServer("surreal", path: "rocksdb://data"); var db1 = surrealServer1 .AddNamespace("ns") @@ -133,7 +133,7 @@ public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume) using var builder2 = TestDistributedApplicationBuilder.Create(testOutputHelper); - var surrealServer2 = builder2.AddSurrealServer("surreal"); + var surrealServer2 = builder2.AddSurrealServer("surreal", path: "rocksdb://data"); var db2 = surrealServer2 .AddNamespace("ns") diff --git a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/AspireSurrealClientExtensionsTest.cs b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/AspireSurrealClientExtensionsTest.cs index ae23da79..6a514221 100644 --- a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/AspireSurrealClientExtensionsTest.cs +++ b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/AspireSurrealClientExtensionsTest.cs @@ -87,9 +87,9 @@ public void CanAddMultipleKeyedServices() var builder = Host.CreateEmptyApplicationBuilder(null); builder.Configuration.AddInMemoryCollection([ - new KeyValuePair("ConnectionStrings:surreal1", "http://localhost:19530"), - new KeyValuePair("ConnectionStrings:surreal2", "http://localhost:19531"), - new KeyValuePair("ConnectionStrings:surreal3", "http://localhost:19532"), + new KeyValuePair("ConnectionStrings:surreal1", "Endpoint=http://localhost:19530"), + new KeyValuePair("ConnectionStrings:surreal2", "Endpoint=http://localhost:19531"), + new KeyValuePair("ConnectionStrings:surreal3", "Endpoint=http://localhost:19532"), ]); builder.AddSurrealClient("surreal1"); From 5cb82c09ab7bd3d663f88b930b12297a03e839c3 Mon Sep 17 00:00:00 2001 From: Odonno Date: Mon, 31 Mar 2025 18:47:22 +0200 Subject: [PATCH 24/30] test: fix `DefaultConnectionString` value --- .../AspireSurrealClientExtensionsTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/AspireSurrealClientExtensionsTest.cs b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/AspireSurrealClientExtensionsTest.cs index 6a514221..20ab869b 100644 --- a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/AspireSurrealClientExtensionsTest.cs +++ b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/AspireSurrealClientExtensionsTest.cs @@ -14,7 +14,7 @@ public class AspireSurrealClientExtensionsTest(SurrealDbContainerFixture contain private const string DefaultConnectionName = "db"; private string DefaultConnectionString => - RequiresDockerAttribute.IsSupported ? containerFixture.GetConnectionString() : "http://localhost:27011"; + RequiresDockerAttribute.IsSupported ? containerFixture.GetConnectionString() : "Endpoint=http://localhost:27011"; [Theory] [InlineData(true)] From a05a8de77203dece80a0996eed455444080ea481 Mon Sep 17 00:00:00 2001 From: Odonno Date: Mon, 31 Mar 2025 18:49:54 +0200 Subject: [PATCH 25/30] fix: rollback some changes in CODEOWNERS file --- CODEOWNERS | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index a0e43348..9341fe0e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -21,7 +21,6 @@ /tests/CommunityToolkit.Aspire.Hosting.Azure.DataApiBuilder.Tests/ @tommasodotNET @FBoucher # CommunityToolkit.Aspire.Meilisearch - # CommunityToolkit.Aspire.Hosting.Meilisearch /examples/meilisearch/ @Alirexaa @@ -43,7 +42,6 @@ /tests/CommunityToolkit.Aspire.Hosting.Rust/ @Alirexaa # CommunityToolkit.Aspire.EventStore - # CommunityToolkit.Aspire.Hosting.EventStore /examples/eventstore/ @fredimachado @@ -64,14 +62,13 @@ /src/CommunityToolkit.Aspire.Hosting.Ngrok/ @esskar /tests/CommunityToolkit.Aspire.Hosting.Ngrok.Tests/ @esskar -# CommunityToolkit.Aspire.Dapr.\* +# CommunityToolkit.Aspire.Dapr.* /examples/dapr**/ @FullStackChef @WhitWaldo @Paule96 /src/CommunityToolkit.Aspire.Hosting.Dapr**/ @FullStackChef @WhitWaldo @Paule96 -/tests/CommunityToolkit.Aspire.Hosting.Dapr\*\*.Tests/ @FullStackChef @WhitWaldo @Paule96 +/tests/CommunityToolkit.Aspire.Hosting.Dapr**.Tests/ @FullStackChef @WhitWaldo @Paule96 # CommunityToolkit.Aspire.RavenDB.Client - # CommunityToolkit.Aspire.Hosting.RavenDB /examples/ravendb/ @shiranshalom @@ -109,10 +106,8 @@ /src/CommunityToolkit.Aspire.Hosting.SqlServer.Extensions/ @Alirexaa /tests/CommunityToolkit.Aspire.Hosting.SqlServer.Extensions.Tests/ @Alirexaa /examples/sqlserver-ext/ @Alirexaa -<<<<<<< HEAD # CommunityToolkit.Aspire.Minio.Client - # CommunityToolkit.Aspire.Hosting.Minio /examples/minio/ @Harold-Morgan @@ -123,7 +118,6 @@ /tests/CommunityToolkit.Aspire.Minio.Client.Tests/ @Harold-Morgan # CommunityToolkit.Aspire.SurrealDb - # CommunityToolkit.Aspire.Hosting.SurrealDb /examples/surrealdb/ @Odonno From b3ff8a3d15108264cb7fec7db27b09e1a561dbf1 Mon Sep 17 00:00:00 2001 From: Odonno Date: Tue, 1 Apr 2025 20:52:58 +0200 Subject: [PATCH 26/30] test: use pasword constant --- .../SurrealDbBuilderExtensions.cs | 63 ++++++------- .../PasswordConstantDefault.cs | 15 +++ .../SurrealDbFunctionalTests.cs | 94 ++++++++++--------- 3 files changed, 94 insertions(+), 78 deletions(-) create mode 100644 tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/PasswordConstantDefault.cs diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs index 2bd85ba4..8849ec0d 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs @@ -2,10 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Aspire.Hosting.ApplicationModel; -using Aspire.Hosting.Utils; -using CommunityToolkit.Aspire.SurrealDb; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Diagnostics.HealthChecks; using SurrealDb.Net; using System.Text.Json; @@ -112,8 +109,8 @@ public static IResourceBuilder AddSurrealServer( /// /// public static IResourceBuilder AddNamespace( - this IResourceBuilder builder, - [ResourceName] string name, + this IResourceBuilder builder, + [ResourceName] string name, string? namespaceName = null ) { @@ -152,8 +149,8 @@ public static IResourceBuilder AddNamespace( /// /// public static IResourceBuilder AddDatabase( - this IResourceBuilder builder, - [ResourceName] string name, + this IResourceBuilder builder, + [ResourceName] string name, string? databaseName = null ) { @@ -175,17 +172,17 @@ 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); }); string namespaceName = builder.Resource.Name; string serverName = builder.Resource.Parent.Name; - + string healthCheckKey = $"{serverName}_{namespaceName}_{name}_check"; builder.ApplicationBuilder.Services.AddHealthChecks().AddSurreal(_ => surrealDbClient!, healthCheckKey); - + return builder.ApplicationBuilder.AddResource(surrealServerDatabase) .WithHealthCheck(healthCheckKey); } @@ -219,7 +216,7 @@ public static IResourceBuilder WithDataVolume(this IRes { ArgumentNullException.ThrowIfNull(builder); - return builder.WithVolume(name ?? VolumeNameGenerator.Generate(builder, "data"), "/var/opt/surreal"); + return builder.WithVolume(name ?? VolumeNameGenerator.Generate(builder, "data"), "/data"); } /// @@ -252,9 +249,9 @@ public static IResourceBuilder WithDataBindMount(this I ArgumentNullException.ThrowIfNull(builder); ArgumentException.ThrowIfNullOrEmpty(source); - return builder.WithBindMount(source, "/var/opt/surreal"); + return builder.WithBindMount(source, "/data"); } - + /// /// Adds a Surrealist UI instance for SurrealDB to the application model. /// The default image is and the tag is . @@ -271,19 +268,19 @@ public static IResourceBuilder WithSurrealist( where T : SurrealDbServerResource { ArgumentNullException.ThrowIfNull(builder); - + if (builder.ApplicationBuilder.Resources.OfType().SingleOrDefault() is { } existingSurrealistResource) { var builderForExistingResource = builder.ApplicationBuilder.CreateResourceBuilder(existingSurrealistResource); configureContainer?.Invoke(builderForExistingResource); - + return builder; } - + containerName ??= $"{builder.Resource.Name}-surrealist"; - + const string CONNECTIONS_FILE_PATH = "/usr/share/nginx/html/connections.json"; - + var surrealistContainer = new SurrealistContainerResource(containerName); var surrealistContainerBuilder = builder.ApplicationBuilder.AddResource(surrealistContainer) .WithImage(SurrealDbContainerImageTags.SurrealistImage, SurrealDbContainerImageTags.SurrealistTag) @@ -292,15 +289,15 @@ public static IResourceBuilder WithSurrealist( .WithBindMount(Path.GetTempFileName(), CONNECTIONS_FILE_PATH) .WithRelationship(builder.Resource, "Surrealist") .ExcludeFromManifest(); - - builder.ApplicationBuilder.Eventing.Subscribe( (e, ct) => + + builder.ApplicationBuilder.Eventing.Subscribe((e, ct) => { var serverFileMount = surrealistContainer.Annotations.OfType().Single(v => v.Target == CONNECTIONS_FILE_PATH); var surrealDbServerResources = builder.ApplicationBuilder.Resources.OfType().ToList(); using var stream = new FileStream(serverFileMount.Source!, FileMode.Create); using var writer = new Utf8JsonWriter(stream); - + // Need to grant read access to the config file on unix like systems. if (!OperatingSystem.IsWindows()) { @@ -316,7 +313,7 @@ public static IResourceBuilder WithSurrealist( } writer.WriteStartArray("connections"); - + var surrealDbNamespaceResources = builder.ApplicationBuilder.Resources.OfType().ToList(); var surrealDbDatabaseResources = builder.ApplicationBuilder.Resources.OfType().ToList(); @@ -326,7 +323,7 @@ public static IResourceBuilder WithSurrealist( { SurrealDbNamespaceResource? uniqueNamespace = null; SurrealDbDatabaseResource? uniqueDatabase = null; - + var serverNamespaces = surrealDbNamespaceResources .Where(ns => ns.Parent == surrealInstance) .ToList(); @@ -334,24 +331,24 @@ public static IResourceBuilder WithSurrealist( if (serverNamespaces.Count == 1) { uniqueNamespace = serverNamespaces.First(); - + var nsDatabases = surrealDbDatabaseResources .Where(db => db.Parent == uniqueNamespace) .ToList(); - + if (nsDatabases.Count == 1) { uniqueDatabase = nsDatabases.First(); } } - + var endpoint = surrealInstance.PrimaryEndpoint; writer.WriteStartObject(); writer.WriteString("id", surrealInstance.Name); writer.WriteString("name", surrealInstance.Name); - + if (uniqueNamespace is not null) { writer.WriteString("defaultNamespace", uniqueNamespace.NamespaceName); @@ -360,7 +357,7 @@ public static IResourceBuilder WithSurrealist( { writer.WriteString("defaultDatabase", uniqueDatabase.DatabaseName); } - + writer.WriteStartObject("authentication"); writer.WriteString("protocol", "ws"); // How to do host resolution? @@ -374,20 +371,20 @@ public static IResourceBuilder WithSurrealist( { writer.WriteString("database", uniqueDatabase.DatabaseName); } - + writer.WriteEndObject(); - + writer.WriteEndObject(); } } writer.WriteEndArray(); - + writer.WriteEndObject(); - + return Task.CompletedTask; }); - + configureContainer?.Invoke(surrealistContainerBuilder); return builder; diff --git a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/PasswordConstantDefault.cs b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/PasswordConstantDefault.cs new file mode 100644 index 00000000..7700939c --- /dev/null +++ b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/PasswordConstantDefault.cs @@ -0,0 +1,15 @@ +using Aspire.Hosting.Publishing; + +namespace CommunityToolkit.Aspire.Hosting.SurrealDb.Tests; + +internal sealed class PasswordConstantDefault : ParameterDefault +{ + public override void WriteToManifest(ManifestPublishingContext context) + { + } + + public override string GetDefaultValue() + { + return "password"; + } +} \ No newline at end of file diff --git a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbFunctionalTests.cs b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbFunctionalTests.cs index a9003378..5be12b13 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbFunctionalTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbFunctionalTests.cs @@ -5,7 +5,6 @@ using Aspire.Hosting; using Aspire.Hosting.Utils; using Bogus; -using CommunityToolkit.Aspire.Testing; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Hosting; using SurrealDb.Net; @@ -22,7 +21,7 @@ public class SurrealDbFunctionalTests(ITestOutputHelper testOutputHelper) static SurrealDbFunctionalTests() { - _todoList = new TodoFaker().Generate(_generatedTodoCount).ToArray(); + _todoList = [.. new TodoFaker().Generate(_generatedTodoCount)]; int index = 0; foreach (var todo in _todoList) @@ -30,10 +29,12 @@ static SurrealDbFunctionalTests() todo.Id = (Todo.Table, (++index).ToString()); } } - + [Fact] public async Task VerifySurrealDbResource() { + var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5)); + var ct = cts.Token; using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); var surrealServer = builder.AddSurrealServer("surreal"); @@ -41,14 +42,12 @@ public async Task VerifySurrealDbResource() var db = surrealServer .AddNamespace("ns") .AddDatabase("db"); - + using var app = builder.Build(); await app.StartAsync(); -#pragma warning disable CTASPIRE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - await app.WaitForTextAsync("Started web server on", surrealServer.Resource.Name); -#pragma warning restore CTASPIRE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + await app.ResourceNotifications.WaitForResourceHealthyAsync(surrealServer.Resource.Name, ct); var hb = Host.CreateApplicationBuilder(); @@ -58,12 +57,12 @@ public async Task VerifySurrealDbResource() using var host = hb.Build(); - await host.StartAsync(); + await host.StartAsync(ct); var surrealDbClient = host.Services.GetRequiredService(); - await CreateTestData(surrealDbClient); - await AssertTestData(surrealDbClient); + await CreateTestData(surrealDbClient, ct); + await AssertTestData(surrealDbClient, ct); } [Theory] @@ -71,19 +70,24 @@ public async Task VerifySurrealDbResource() [InlineData(false)] public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume) { + var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5)); + string? volumeName = null; string? bindMountPath = null; try { using var builder1 = TestDistributedApplicationBuilder.Create(testOutputHelper); - - var surrealServer1 = builder1.AddSurrealServer("surreal", path: "rocksdb://data"); + + var password1 = builder1.AddParameter("surreal-password", secret: true); + password1.Resource.Default = new PasswordConstantDefault(); + + var surrealServer1 = builder1.AddSurrealServer("surreal", path: "rocksdb://data/db.db", password: password1); var db1 = surrealServer1 .AddNamespace("ns") .AddDatabase("db"); - + if (useVolume) { // Use a deterministic volume name to prevent them from exhausting the machines if deletion fails @@ -97,48 +101,52 @@ public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume) { bindMountPath = Directory.CreateTempSubdirectory().FullName; surrealServer1.WithDataBindMount(bindMountPath); + + if (!OperatingSystem.IsWindows()) + { + File.SetUnixFileMode(bindMountPath, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute | UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.OtherExecute); + } } using (var app = builder1.Build()) { - await app.StartAsync(); + await app.StartAsync(cts.Token); -#pragma warning disable CTASPIRE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - await app.WaitForTextAsync("Started web server on", surrealServer1.Resource.Name); -#pragma warning restore CTASPIRE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + await app.ResourceNotifications.WaitForResourceHealthyAsync(surrealServer1.Resource.Name, cts.Token); try { var hb = Host.CreateApplicationBuilder(); - hb.Configuration[$"ConnectionStrings:{db1.Resource.Name}"] = await db1.Resource.ConnectionStringExpression.GetValueAsync(default); + hb.Configuration[$"ConnectionStrings:{db1.Resource.Name}"] = await db1.Resource.ConnectionStringExpression.GetValueAsync(cts.Token); hb.AddSurrealClient(db1.Resource.Name); - using (var host = hb.Build()) - { - await host.StartAsync(); + using var host = hb.Build(); + await host.StartAsync(cts.Token); - var surrealDbClient = host.Services.GetRequiredService(); - await CreateTestData(surrealDbClient); - await AssertTestData(surrealDbClient); - } + await using var surrealDbClient = host.Services.GetRequiredService(); + await CreateTestData(surrealDbClient, cts.Token); + await AssertTestData(surrealDbClient, cts.Token); } finally { // Stops the container, or the Volume would still be in use - await app.StopAsync(); + await app.StopAsync(cts.Token); } } using var builder2 = TestDistributedApplicationBuilder.Create(testOutputHelper); - - var surrealServer2 = builder2.AddSurrealServer("surreal", path: "rocksdb://data"); + var password2 = builder2.AddParameter("surreal-password", secret: true); + password2.Resource.Default = new PasswordConstantDefault(); + + var surrealServer2 = builder2.AddSurrealServer("surreal", path: "rocksdb://data/db.db", password: password2); + var db2 = surrealServer2 .AddNamespace("ns") .AddDatabase("db"); - + if (useVolume) { surrealServer2.WithDataVolume(volumeName); @@ -150,31 +158,27 @@ public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume) using (var app = builder2.Build()) { - await app.StartAsync(); + await app.StartAsync(cts.Token); -#pragma warning disable CTASPIRE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - await app.WaitForTextAsync("Started web server on", surrealServer2.Resource.Name); -#pragma warning restore CTASPIRE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + await app.ResourceNotifications.WaitForResourceHealthyAsync(surrealServer2.Resource.Name, cts.Token); try { var hb = Host.CreateApplicationBuilder(); - hb.Configuration[$"ConnectionStrings:{db2.Resource.Name}"] = await db2.Resource.ConnectionStringExpression.GetValueAsync(default); + hb.Configuration[$"ConnectionStrings:{db2.Resource.Name}"] = await db2.Resource.ConnectionStringExpression.GetValueAsync(cts.Token); hb.AddSurrealClient(db2.Resource.Name); - using (var host = hb.Build()) - { - await host.StartAsync(); - var surrealDbClient = host.Services.GetRequiredService(); - await AssertTestData(surrealDbClient); - } + using var host = hb.Build(); + await host.StartAsync(cts.Token); + await using var surrealDbClient = host.Services.GetRequiredService(); + await AssertTestData(surrealDbClient, cts.Token); } finally { // Stops the container, or the Volume would still be in use - await app.StopAsync(); + await app.StopAsync(cts.Token); } } @@ -239,12 +243,12 @@ public async Task VerifyWaitForOnSurrealDbBlocksDependentResources() await app.StopAsync(); } - private static async Task CreateTestData(SurrealDbClient surrealDbClient) + private static async Task CreateTestData(SurrealDbClient surrealDbClient, CancellationToken ct) { - await surrealDbClient.Insert(Todo.Table, _todoList); + await surrealDbClient.Insert(Todo.Table, _todoList, ct); } - private static async Task AssertTestData(SurrealDbClient surrealDbClient) + private static async Task AssertTestData(SurrealDbClient surrealDbClient, CancellationToken ct) { var records = await surrealDbClient.Select(Todo.Table); Assert.Equal(_generatedTodoCount, records.Count()); @@ -253,7 +257,7 @@ private static async Task AssertTestData(SurrealDbClient surrealDbClient) Assert.NotNull(firstRecord); Assert.Equivalent(firstRecord, _todoList[0]); } - + private sealed class Todo : SurrealRecord { internal const string Table = "todo"; From d72f302306607e5d921041b6783c7f015c928b6c Mon Sep 17 00:00:00 2001 From: Odonno Date: Wed, 16 Apr 2025 12:07:38 +0200 Subject: [PATCH 27/30] refactor: support newer version of Surrealist --- .../SurrealDbBuilderExtensions.cs | 8 +------- .../SurrealDbContainerImageTags.cs | 4 ++-- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs index 8849ec0d..44d43df4 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs @@ -279,7 +279,7 @@ public static IResourceBuilder WithSurrealist( containerName ??= $"{builder.Resource.Name}-surrealist"; - const string CONNECTIONS_FILE_PATH = "/usr/share/nginx/html/connections.json"; + const string CONNECTIONS_FILE_PATH = "/usr/share/nginx/html/instance.json"; var surrealistContainer = new SurrealistContainerResource(containerName); var surrealistContainerBuilder = builder.ApplicationBuilder.AddResource(surrealistContainer) @@ -306,12 +306,6 @@ public static IResourceBuilder WithSurrealist( writer.WriteStartObject(); - if (surrealDbServerResources.Count == 1) - { - var uniqueSurrealDbResource = surrealDbServerResources[0]; - writer.WriteString("defaultConnection", uniqueSurrealDbResource.Name); - } - writer.WriteStartArray("connections"); var surrealDbNamespaceResources = builder.ApplicationBuilder.Resources.OfType().ToList(); diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbContainerImageTags.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbContainerImageTags.cs index b605b584..649cabc0 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbContainerImageTags.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbContainerImageTags.cs @@ -16,6 +16,6 @@ internal sealed class SurrealDbContainerImageTags public const string SurrealistRegistry = "docker.io"; /// surrealdb/surrealist public const string SurrealistImage = "surrealdb/surrealist"; - /// 3.3.0 - public const string SurrealistTag = "3.3.0"; + /// 3.3.2 + public const string SurrealistTag = "3.3.2"; } \ No newline at end of file From 0c382ed821a18378ac425fa5afdaf728d8d87add Mon Sep 17 00:00:00 2001 From: Odonno Date: Wed, 4 Jun 2025 17:59:30 +0200 Subject: [PATCH 28/30] ci: fix duplicate package versions --- Directory.Packages.props | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 96335190..05ffba9c 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -83,6 +83,7 @@ + @@ -99,8 +100,6 @@ - - From 85169bb2b69188ab2840d3e5d6f2b9b15e7eace7 Mon Sep 17 00:00:00 2001 From: Odonno Date: Wed, 4 Jun 2025 18:00:50 +0200 Subject: [PATCH 29/30] ci: fix tests list --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 3c9c51c2..bfbc4865 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -33,6 +33,7 @@ jobs: Hosting.GoFeatureFlag.Tests, Hosting.Golang.Tests, Hosting.Java.Tests, + Hosting.k6.Tests, Hosting.LavinMQ.Tests, Hosting.MailPit.Tests, Hosting.Meilisearch.Tests, @@ -51,7 +52,6 @@ jobs: Hosting.SqlDatabaseProjects.Tests, Hosting.SqlServer.Extensions.Tests, Hosting.Sqlite.Tests, - Hosting.k6.Tests, Hosting.Minio.Tests, Hosting.SurrealDb.Tests, From 99c1482224b215273e2f7ed99532a2f50b0a91a2 Mon Sep 17 00:00:00 2001 From: Odonno Date: Wed, 4 Jun 2025 18:43:56 +0200 Subject: [PATCH 30/30] chore: allow overrided container runtime when attempting to remove docker volume --- tests/CommunityToolkit.Aspire.Testing/DockerUtils.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/CommunityToolkit.Aspire.Testing/DockerUtils.cs b/tests/CommunityToolkit.Aspire.Testing/DockerUtils.cs index 83d58e49..8bacff0f 100644 --- a/tests/CommunityToolkit.Aspire.Testing/DockerUtils.cs +++ b/tests/CommunityToolkit.Aspire.Testing/DockerUtils.cs @@ -9,6 +9,8 @@ public sealed class DockerUtils { public static void AttemptDeleteDockerVolume(string volumeName, bool throwOnFailure = false) { + string containerRuntime = Environment.GetEnvironmentVariable("DOTNET_ASPIRE_CONTAINER_RUNTIME") ?? "docker"; + for (var i = 0; i < 3; i++) { if (i != 0) @@ -16,7 +18,7 @@ public static void AttemptDeleteDockerVolume(string volumeName, bool throwOnFail Thread.Sleep(1000); } - if (Process.Start("docker", $"volume rm {volumeName}") is { } process) + if (Process.Start(containerRuntime, $"volume rm {volumeName}") is { } process) { var exited = process.WaitForExit(TimeSpan.FromSeconds(3)); var done = exited && process.ExitCode == 0; @@ -32,7 +34,7 @@ public static void AttemptDeleteDockerVolume(string volumeName, bool throwOnFail if (throwOnFailure) { - if (Process.Start("docker", $"volume inspect {volumeName}") is { } process) + if (Process.Start(containerRuntime, $"volume inspect {volumeName}") is { } process) { var exited = process.WaitForExit(TimeSpan.FromSeconds(3)); var exitCode = process.ExitCode;