Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add surrealdb component #5721

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Aspire.sln
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Hosting.Sdk.Tests",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.RuntimeIdentifier.Tool", "src\Aspire.Hosting.Sdk\Aspire.RuntimeIdentifier.Tool\Aspire.RuntimeIdentifier.Tool.csproj", "{FF2895FC-A613-43DC-96B8-E5DFA69125CA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Hosting.SurrealDb", "src\Aspire.Hosting.SurrealDb\Aspire.Hosting.SurrealDb.csproj", "{DDA7BFD3-3C8E-4176-A9BF-FBF8D11504F0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Hosting.SurrealDb.Tests", "tests\Aspire.Hosting.SurrealDb.Tests\Aspire.Hosting.SurrealDb.Tests.csproj", "{13F02813-8EEA-492D-AACC-65F5D758CECA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -1637,6 +1641,14 @@ Global
{FF2895FC-A613-43DC-96B8-E5DFA69125CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FF2895FC-A613-43DC-96B8-E5DFA69125CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FF2895FC-A613-43DC-96B8-E5DFA69125CA}.Release|Any CPU.Build.0 = Release|Any CPU
{DDA7BFD3-3C8E-4176-A9BF-FBF8D11504F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DDA7BFD3-3C8E-4176-A9BF-FBF8D11504F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DDA7BFD3-3C8E-4176-A9BF-FBF8D11504F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DDA7BFD3-3C8E-4176-A9BF-FBF8D11504F0}.Release|Any CPU.Build.0 = Release|Any CPU
{13F02813-8EEA-492D-AACC-65F5D758CECA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{13F02813-8EEA-492D-AACC-65F5D758CECA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{13F02813-8EEA-492D-AACC-65F5D758CECA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{13F02813-8EEA-492D-AACC-65F5D758CECA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1937,6 +1949,8 @@ Global
{6249A193-3BF4-4FFB-AB81-6590A8318889} = {874EA351-05EA-44F5-8B12-7A7F865ECEC5}
{AEF07BC6-76D8-4A45-89D5-54FC4483863F} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60}
{FF2895FC-A613-43DC-96B8-E5DFA69125CA} = {F534D4F8-5E3A-42FC-BCD7-4C2D6060F9C8}
{DDA7BFD3-3C8E-4176-A9BF-FBF8D11504F0} = {B80354C7-BE58-43F6-8928-9F3A74AB7F47}
{13F02813-8EEA-492D-AACC-65F5D758CECA} = {830A89EC-4029-4753-B25A-068BAE37DEC7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6DCEDFEC-988E-4CB3-B45B-191EB5086E0C}
Expand Down
24 changes: 24 additions & 0 deletions src/Aspire.Hosting.SurrealDb/Aspire.Hosting.SurrealDb.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>$(DefaultTargetFramework)</TargetFramework>
<IsPackable>true</IsPackable>
<PackageTags>aspire integration hosting surrealdb</PackageTags>
<Description>SurrealDB support for .NET Aspire.</Description>
<PackageIconFullPath>$(SharedDir)surrealdb.png</PackageIconFullPath>
Odonno marked this conversation as resolved.
Show resolved Hide resolved
</PropertyGroup>

<PropertyGroup>
<MinCodeCoverage>25</MinCodeCoverage>
</PropertyGroup>

<ItemGroup>
<Compile Include="$(SharedDir)StringComparers.cs" Link="Utils\StringComparers.cs" />
<Compile Include="$(SharedDir)VolumeNameGenerator.cs" Link="Utils\VolumeNameGenerator.cs" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Aspire.Hosting\Aspire.Hosting.csproj" />
</ItemGroup>

</Project>
1 change: 1 addition & 0 deletions src/Aspire.Hosting.SurrealDb/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#nullable enable
26 changes: 26 additions & 0 deletions src/Aspire.Hosting.SurrealDb/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -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<string!, string!>!
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<string?>
Aspire.Hosting.ApplicationModel.SurrealDbServerResource.Namespaces.get -> System.Collections.Generic.IReadOnlyDictionary<string!, string!>!
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<Aspire.Hosting.ApplicationModel.SurrealDbNamespaceResource!>! builder, string! name, string? databaseName = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.SurrealDbDatabaseResource!>!
static Aspire.Hosting.SurrealDbBuilderExtensions.AddNamespace(this Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.SurrealDbServerResource!>! builder, string! name, string? namespaceName = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.SurrealDbNamespaceResource!>!
static Aspire.Hosting.SurrealDbBuilderExtensions.AddSurrealServer(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name, Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.ParameterResource!>? userName = null, Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.ParameterResource!>? password = null, int? port = null, bool strictMode = false) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.SurrealDbServerResource!>!
static Aspire.Hosting.SurrealDbBuilderExtensions.WithDataBindMount(this Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.SurrealDbServerResource!>! builder, string! source, bool isReadOnly = false) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.SurrealDbServerResource!>!
static Aspire.Hosting.SurrealDbBuilderExtensions.WithDataVolume(this Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.SurrealDbServerResource!>! builder, string? name = null, bool isReadOnly = false) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.SurrealDbServerResource!>!
34 changes: 34 additions & 0 deletions src/Aspire.Hosting.SurrealDb/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Aspire.Hosting.SurrealDb library

Provides extension methods and resource definitions for a .NET Aspire AppHost to configure a SurrealDB database resource.

## 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 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<Projects.MyService>()
.WithReference(db);
```

## Additional documentation

https://learn.microsoft.com/dotnet/aspire/database/surrealdb-component

## Feedback & contributing

https://github.com/dotnet/aspire
148 changes: 148 additions & 0 deletions src/Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// 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;

namespace Aspire.Hosting;

/// <summary>
/// Provides extension methods for adding SurrealDB resources to the application model.
/// </summary>
public static class SurrealDbBuilderExtensions
{
private const string UserEnvVarName = "SURREAL_USER";
private const string PasswordEnvVarName = "SURREAL_PASS";

/// <summary>
/// Adds a SurrealDB resource to the application model. A container is used for local development.
/// </summary>
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
/// <param name="userName">The parameter used to provide the administrator username for the SurrealDB resource.</param>
/// <param name="password">The parameter used to provide the administrator password for the SurrealDB resource. If <see langword="null"/> a random password will be generated.</param>
/// <param name="port">The host port for the SurrealDB instance.</param>
/// <param name="strictMode">Whether strict mode is enabled on the server.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<SurrealDbServerResource> AddSurrealServer(
this IDistributedApplicationBuilder builder,
string name,
IResourceBuilder<ParameterResource>? userName = null,
IResourceBuilder<ParameterResource>? password = null,
int? port = null,
bool strictMode = false
)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentException.ThrowIfNullOrEmpty(name);

var args = new List<string>
{
"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)
Odonno marked this conversation as resolved.
Show resolved Hide resolved
.WithImageRegistry(SurrealDbContainerImageTags.Registry)
.WithEnvironment(context =>
{
context.EnvironmentVariables[UserEnvVarName] = surrealServer.UserNameReference;
context.EnvironmentVariables[PasswordEnvVarName] = surrealServer.PasswordParameter;
})
.WithEntrypoint("/surreal")
.WithArgs([.. args]);
}

/// <summary>
/// Adds a SurrealDB namespace to the application model. This is a child resource of a <see cref="SurrealDbServerResource"/>.
/// </summary>
/// <param name="builder">The SurrealDB resource builders.</param>
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
/// <param name="namespaceName">The name of the namespace. If not provided, this defaults to the same value as <paramref name="name"/>.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<SurrealDbNamespaceResource> AddNamespace(this IResourceBuilder<SurrealDbServerResource> builder, 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);
}

/// <summary>
/// Adds a SurrealDB database to the application model. This is a child resource of a <see cref="SurrealDbNamespaceResource"/>.
/// </summary>
/// <param name="builder">The SurrealDB resource builders.</param>
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
/// <param name="databaseName">The name of the database. If not provided, this defaults to the same value as <paramref name="name"/>.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<SurrealDbDatabaseResource> AddDatabase(this IResourceBuilder<SurrealDbNamespaceResource> builder, 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);
Odonno marked this conversation as resolved.
Show resolved Hide resolved
var surrealServerDatabase = new SurrealDbDatabaseResource(name, databaseName, builder.Resource);

string? connectionString = null;

builder.ApplicationBuilder.Eventing.Subscribe<ConnectionStringAvailableEvent>(surrealServerDatabase, async (@event, ct) =>
{
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.");
}
});

// TODO : Add HealthChecks, waiting for https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/pull/2047

return builder.ApplicationBuilder.AddResource(surrealServerDatabase);
}

/// <summary>
/// Adds a named volume for the data folder to a SurrealDB resource.
/// </summary>
/// <param name="builder">The resource builder.</param>
/// <param name="name">The name of the volume. Defaults to an auto-generated name based on the application and resource names.</param>
/// <param name="isReadOnly">A flag that indicates if this is a read-only volume.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<SurrealDbServerResource> WithDataVolume(this IResourceBuilder<SurrealDbServerResource> builder, string? name = null, bool isReadOnly = false)
{
ArgumentNullException.ThrowIfNull(builder);

return builder.WithVolume(name ?? VolumeNameGenerator.CreateVolumeName(builder, "data"), "/var/opt/surreal", isReadOnly);
}

/// <summary>
/// Adds a bind mount for the data folder to a SurrealDB resource.
/// </summary>
/// <param name="builder">The resource builder.</param>
/// <param name="source">The source directory on the host to mount into the container.</param>
/// <param name="isReadOnly">A flag that indicates if this is a read-only mount.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<SurrealDbServerResource> WithDataBindMount(this IResourceBuilder<SurrealDbServerResource> builder, string source, bool isReadOnly = false)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentException.ThrowIfNullOrEmpty(source);

return builder.WithBindMount(source, "/var/opt/surreal", isReadOnly);
}
}
11 changes: 11 additions & 0 deletions src/Aspire.Hosting.SurrealDb/SurrealDbContainerImageTags.cs
Original file line number Diff line number Diff line change
@@ -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.1";
}
Odonno marked this conversation as resolved.
Show resolved Hide resolved
41 changes: 41 additions & 0 deletions src/Aspire.Hosting.SurrealDb/SurrealDbDatabaseResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// 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;

/// <summary>
/// A resource that represents a SurrealDB database that is a child of a SurrealDB namespace resource.
/// </summary>
public class SurrealDbDatabaseResource : Resource, IResourceWithParent<SurrealDbNamespaceResource>, IResourceWithConnectionString
{
/// <summary>
/// Gets the parent SurrealDB namespace resource.
/// </summary>
public SurrealDbNamespaceResource Parent { get; }

/// <summary>
/// Gets the connection string expression for the SurrealDB database.
/// </summary>
public ReferenceExpression ConnectionStringExpression =>
ReferenceExpression.Create($"{Parent};Database={DatabaseName}");

/// <summary>
/// Gets the database name.
/// </summary>
public string DatabaseName { get; }

/// <summary>
/// Initializes a new instance of the <see cref="SurrealDbDatabaseResource"/> class.
/// </summary>
/// <param name="name">The name of the resource.</param>
/// <param name="databaseName">The database name.</param>
/// <param name="parent">The parent SurrealDB namespace resource.</param>
public SurrealDbDatabaseResource(string name, string databaseName, SurrealDbNamespaceResource parent) : base(name)
{
ArgumentException.ThrowIfNullOrEmpty(databaseName);
ArgumentNullException.ThrowIfNull(parent);

DatabaseName = databaseName;
Parent = parent;
}
}
Loading