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

Add Meilisearch integration #66

Merged
merged 25 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2b79293
Add Meilisearch hosting
Alirexaa Oct 4, 2024
316a4cd
Merge branch 'main' into alirexaa/meilisearch
Alirexaa Oct 17, 2024
b6123d7
Add public api files
Alirexaa Oct 17, 2024
3e8da2f
Clean up csproj
Alirexaa Oct 17, 2024
4b3ef7d
remove configuration schema stuff
Alirexaa Oct 17, 2024
2957152
add tests
Alirexaa Oct 17, 2024
78bc644
fix build
Alirexaa Oct 17, 2024
a05fedb
Fix test by addib Aspire.Hosting.AppHost to Testing proj
Alirexaa Oct 17, 2024
f27d9b9
Add test for client package
Alirexaa Oct 17, 2024
6d80111
fix test on windows
Alirexaa Oct 17, 2024
42d83ca
Add example
Alirexaa Oct 17, 2024
38a3881
Address PR feedback
Alirexaa Oct 18, 2024
40979ca
Add logging for tests
Alirexaa Oct 18, 2024
ab70c71
Add readme for packages
Alirexaa Oct 18, 2024
b46bf3e
fix xml docs
Alirexaa Oct 18, 2024
89462d4
rename file
Alirexaa Oct 18, 2024
a01d3ff
Merge branch 'main' into alirexaa/meilisearch
Alirexaa Oct 18, 2024
45792d3
Remove xunit test classes and use Microsoft.DotNet.XUnitExtensions in…
Alirexaa Oct 21, 2024
a4a4ce0
Merge branch 'alirexaa/meilisearch' of https://github.com/CommunityTo…
Alirexaa Oct 21, 2024
84f4af2
Merge branch 'main' into alirexaa/meilisearch
Alirexaa Oct 21, 2024
f09e9be
Remove polly and use WaitForText instead
Alirexaa Oct 21, 2024
f200d81
Merge branch 'alirexaa/meilisearch' of https://github.com/CommunityTo…
Alirexaa Oct 21, 2024
4fd4699
Update codeowners
Alirexaa Oct 21, 2024
d4a4d28
Merge branch 'main' into alirexaa/meilisearch
aaronpowell Oct 22, 2024
2ee554c
Matching the correct naming structure
aaronpowell Oct 22, 2024
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
68 changes: 48 additions & 20 deletions Aspire.CommunityToolkit.sln

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
<AspireVersion>8.2.0</AspireVersion>
<AspNetCoreVersion>8.0.7</AspNetCoreVersion>
<OpenTelemetryVersion>1.9.0</OpenTelemetryVersion>

<TestcontainersVersion>3.10.0</TestcontainersVersion>
Alirexaa marked this conversation as resolved.
Show resolved Hide resolved
<IsPackable>false</IsPackable>
<UsePublicApiAnalyzers>true</UsePublicApiAnalyzers>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>

<RepoRoot>$(MSBuildThisFileDirectory)</RepoRoot>
</PropertyGroup>
</Project>
6 changes: 6 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
<PackageVersion Include="JsonSchema.Net" Version="7.2.3" />
<!-- AspNetCore packages -->
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="$(AspNetCoreVersion)" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.6.2" />
<!-- .NET packages -->
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="8.7.0" />
Expand All @@ -33,9 +35,13 @@
<PackageVersion Include="xunit" Version="2.9.1" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageVersion Include="xunit.extensibility.execution" Version="2.9.1" />
<PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" />
<!-- External packages -->
<PackageVersion Include="OllamaSharp" Version="3.0.7" />
<PackageVersion Include="MeiliSearch" Version="0.15.3" />
<!-- Build dependencies -->
<PackageVersion Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="3.3.4" />
<!-- Testcontainers packages -->
<PackageVersion Include="Testcontainers" Version="$(TestcontainersVersion)" />
Alirexaa marked this conversation as resolved.
Show resolved Hide resolved
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<AdditionalPackageTags>integration hosting meilisearch</AdditionalPackageTags>
<Description>Meilisearch support for .NET Aspire.</Description>
</PropertyGroup>

<ItemGroup>
<Compile Include="..\VolumeNameGenerator.cs" Link="Utils\VolumeNameGenerator.cs" />
<!--<Compile Include="$(ComponentsDir)Aspire.Meilisearch\MeilisearchHealthCheck.cs" Link="MeilisearchHealthCheck.cs"></Compile>-->
aaronpowell marked this conversation as resolved.
Show resolved Hide resolved
</ItemGroup>

<ItemGroup>
<PackageReference Include="MeiliSearch" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
using System.Data.Common;
using Aspire.CommunityToolkit.Hosting.Meilisearch;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Utils;
using Meilisearch;

namespace Aspire.Hosting;

/// <summary>
/// Provides extension methods for adding Meilisearch resources to the application model.
/// </summary>
public static class MeilisearchBuilderExtensions
{
private const int MeilisearchPort = 7700;

/// <summary>
/// Adds an Meilisearch container resource to the application model.
/// </summary>
/// <remarks>
/// The default image is <inheritdoc cref="MeilisearchContainerImageTags.Image"/> and the tag is <inheritdoc cref="MeilisearchContainerImageTags.Tag"/>.
/// </remarks>
/// <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="port">The host port to bind the underlying container to.</param>
/// <param name="masterKey">The parameter used to provide the master key for the Meilisearch. If <see langword="null"/> a random master key will be generated.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
/// <example>
/// Add an Meilisearch container to the application model and reference it in a .NET project.
/// <code lang="csharp">
/// var builder = DistributedApplication.CreateBuilder(args);
///
/// var meilisearch = builder.AddMeilisearch("meilisearch");
/// var api = builder.AddProject&lt;Projects.Api&gt;("api")
/// .WithReference(meilisearch);
///
/// builder.Build().Run();
/// </code>
/// </example>
public static IResourceBuilder<MeilisearchResource> AddMeilisearch(
this IDistributedApplicationBuilder builder,
string name,
IResourceBuilder<ParameterResource>? masterKey = null,
int? port = null)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(name);

var masterKeyParameter = masterKey?.Resource ?? ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder, $"{name}-masterKey");

var meilisearch = new MeilisearchResource(name, masterKeyParameter);

//following commented section need to aspire 9.0.

//MeilisearchClient? meilisearchClient = null;

//builder.Eventing.Subscribe<ConnectionStringAvailableEvent>(meilisearch, async (@event, ct) =>
//{
// var connectionString = await meilisearch.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false)
// ?? throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{meilisearch.Name}' resource but the connection string was null.");

// meilisearchClient = CreateMeilisearchClient(connectionString);
//});

//var healthCheckKey = $"{name}_check";
//builder.Services.AddHealthChecks()
// .Add(new HealthCheckRegistration(
// healthCheckKey,
// sp => new MeilisearchHealthCheck(meilisearchClient!),
// failureStatus: default,
// tags: default,
// timeout: default));

return builder.AddResource(meilisearch)
.WithImage(MeilisearchContainerImageTags.Image, MeilisearchContainerImageTags.Tag)
.WithImageRegistry(MeilisearchContainerImageTags.Registry)
.WithHttpEndpoint(targetPort: MeilisearchPort, port: port, name: MeilisearchResource.PrimaryEndpointName)
.WithEnvironment(context =>
{
context.EnvironmentVariables["MEILI_MASTER_KEY"] = meilisearch.MasterKeyParameter;
});
//.WithHealthCheck(healthCheckKey);
}

/// <summary>
/// Adds a named volume for the data folder to a Meilisearch container 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>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
/// <example>
/// 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.
/// <code lang="csharp">
/// var builder = DistributedApplication.CreateBuilder(args);
///
/// var meilisearch = builder.AddMeilisearch("meilisearch")
/// .WithDataVolume();
/// var api = builder.AddProject&lt;Projects.Api&gt;("api")
/// .WithReference(meilisearch);
///
/// builder.Build().Run();
/// </code>
/// </example>
public static IResourceBuilder<MeilisearchResource> WithDataVolume(this IResourceBuilder<MeilisearchResource> 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"), "/meili_data");
#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.
}

/// <summary>
/// Adds a bind mount for the data folder to a Meilisearch container resource.
/// </summary>
/// <param name="builder">The resource builder.</param>
/// <param name="source">The source directory on the host to mount into the container.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
/// <example>
/// Add an Meilisearch 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.
/// <code lang="csharp">
/// var builder = DistributedApplication.CreateBuilder(args);
///
/// var meilisearch = builder.AddMeilisearch("meilisearch")
/// .WithDataBindMount("./data/meilisearch/data");
/// var api = builder.AddProject&lt;Projects.Api&gt;("api")
/// .WithReference(meilisearch);
///
/// builder.Build().Run();
/// </code>
/// </example>
public static IResourceBuilder<MeilisearchResource> WithDataBindMount(this IResourceBuilder<MeilisearchResource> builder, string source)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(source);

return builder.WithBindMount(source, "/meili_data");
}

internal static MeilisearchClient CreateMeilisearchClient(string? connectionString)
{
if (connectionString is null)
{
throw new InvalidOperationException("Connection string is unavailable");
}

Uri? endpoint = null;
string? masterKey = null;

if (Uri.TryCreate(connectionString, UriKind.Absolute, out var uri))
{
endpoint = uri;
}
else
{
var connectionBuilder = new DbConnectionStringBuilder
{
ConnectionString = connectionString
};

if (connectionBuilder.TryGetValue("Endpoint", out var endpointValue) && Uri.TryCreate(endpointValue.ToString(), UriKind.Absolute, out var serviceUri))
{
endpoint = serviceUri;
}

if (connectionBuilder.TryGetValue("MasterKey", out var masterKeyValue))
{
masterKey = masterKeyValue.ToString();
}
}

return new MeilisearchClient(endpoint!.ToString(), apiKey: masterKey!);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Aspire.CommunityToolkit.Hosting.Meilisearch;

internal static class MeilisearchContainerImageTags
{
/// <summary>docker.io</summary>
public const string Registry = "docker.io";
/// <summary>getmeili/meilisearch</summary>
public const string Image = "getmeili/meilisearch";
/// <summary>v1.10</summary>
public const string Tag = "v1.10";
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
namespace Aspire.Hosting.ApplicationModel;

/// <summary>
/// A resource that represents a Meilisearch
/// </summary>
public class MeilisearchResource : ContainerResource, IResourceWithConnectionString
{
internal const string PrimaryEndpointName = "http";

/// <param name="name">The name of the resource.</param>
/// <param name="masterKey">A parameter that contains the Meilisearch master key.</param>
public MeilisearchResource(string name, ParameterResource masterKey) : base(name)
{
ArgumentNullException.ThrowIfNull(masterKey);
MasterKeyParameter = masterKey;
}

private EndpointReference? _primaryEndpoint;

/// <summary>
/// Gets the primary endpoint for the Meilisearch. This endpoint is used for all API calls over HTTP.
/// </summary>
public EndpointReference PrimaryEndpoint => _primaryEndpoint ??= new(this, PrimaryEndpointName);

/// <summary>
/// Gets the parameter that contains the Meilisearch superuser password.
/// </summary>
public ParameterResource MasterKeyParameter { get; }

/// <summary>
/// Gets the connection string expression for the Meilisearch
/// </summary>
public ReferenceExpression ConnectionStringExpression =>
ReferenceExpression.Create($"Endpoint=http://{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)};MasterKey={MasterKeyParameter}");
}

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#nullable enable
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#nullable enable
Aspire.Hosting.ApplicationModel.MeilisearchResource
Aspire.Hosting.ApplicationModel.MeilisearchResource.ConnectionStringExpression.get -> Aspire.Hosting.ApplicationModel.ReferenceExpression!
Aspire.Hosting.ApplicationModel.MeilisearchResource.MasterKeyParameter.get -> Aspire.Hosting.ApplicationModel.ParameterResource!
Aspire.Hosting.ApplicationModel.MeilisearchResource.MeilisearchResource(string! name, Aspire.Hosting.ApplicationModel.ParameterResource! masterKey) -> void
Aspire.Hosting.ApplicationModel.MeilisearchResource.PrimaryEndpoint.get -> Aspire.Hosting.ApplicationModel.EndpointReference!
Aspire.Hosting.MeilisearchBuilderExtensions
static Aspire.Hosting.MeilisearchBuilderExtensions.AddMeilisearch(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name, Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.ParameterResource!>? masterKey = null, int? port = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.MeilisearchResource!>!
static Aspire.Hosting.MeilisearchBuilderExtensions.WithDataBindMount(this Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.MeilisearchResource!>! builder, string! source) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.MeilisearchResource!>!
static Aspire.Hosting.MeilisearchBuilderExtensions.WithDataVolume(this Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.MeilisearchResource!>! builder, string? name = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.MeilisearchResource!>!
1 change: 1 addition & 0 deletions src/Aspire.CommunityToolkit.Hosting.Meilisearch/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
## TODO
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<AdditionalPackageTags>Meilisearch</AdditionalPackageTags>
<Description>A Meilisearch client that integrates with Aspire, including health checks, logging, and telemetry.</Description>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="MeiliSearch" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
</ItemGroup>

<ItemGroup>
<Compile Include="..\HealthChecksExtensions.cs" Link="Utils\HealthChecksExtensions.cs" />
Alirexaa marked this conversation as resolved.
Show resolved Hide resolved
</ItemGroup>

</Project>
Loading
Loading