Skip to content

Feature/solr integration #802

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

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
7 changes: 6 additions & 1 deletion CommunityToolkit.Aspire.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@
<Project Path="examples/rust/CommunityToolkit.Aspire.Hosting.Rust.AppHost/CommunityToolkit.Aspire.Hosting.Rust.AppHost.csproj" />
<Project Path="examples/rust/CommunityToolkit.Aspire.Hosting.Rust.ServiceDefaults/CommunityToolkit.Aspire.Hosting.Rust.ServiceDefaults.csproj" />
</Folder>
<Folder Name="/examples/solr/">
<Project Path="examples/solr/CommunityToolkit.Aspire.Hosting.Solr.AppHost.csproj" />
</Folder>
<Folder Name="/examples/sql-database-projects/">
<Project Path="examples/sql-database-projects/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.AppHost/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.AppHost.csproj" />
<Project Path="examples/sql-database-projects/SdkProject/SdkProject.csproj" />
Expand Down Expand Up @@ -179,6 +182,7 @@
<Project Path="src/CommunityToolkit.Aspire.Hosting.RavenDB/CommunityToolkit.Aspire.Hosting.RavenDB.csproj" />
<Project Path="src/CommunityToolkit.Aspire.Hosting.Redis.Extensions/CommunityToolkit.Aspire.Hosting.Redis.Extensions.csproj" />
<Project Path="src/CommunityToolkit.Aspire.Hosting.Rust/CommunityToolkit.Aspire.Hosting.Rust.csproj" />
<Project Path="src/CommunityToolkit.Aspire.Hosting.Solr/CommunityToolkit.Aspire.Hosting.Solr.csproj" />
<Project Path="src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.csproj" />
<Project Path="src/CommunityToolkit.Aspire.Hosting.Sqlite/CommunityToolkit.Aspire.Hosting.Sqlite.csproj" />
<Project Path="src/CommunityToolkit.Aspire.Hosting.SqlServer.Extensions/CommunityToolkit.Aspire.Hosting.SqlServer.Extensions.csproj" />
Expand Down Expand Up @@ -228,6 +232,7 @@
<Project Path="tests/CommunityToolkit.Aspire.Hosting.RavenDB.Tests/CommunityToolkit.Aspire.Hosting.RavenDB.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.Redis.Extensions.Tests/CommunityToolkit.Aspire.Hosting.Redis.Extensions.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.Rust.Tests/CommunityToolkit.Aspire.Hosting.Rust.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.Solr.Tests/CommunityToolkit.Aspire.Hosting.Solr.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.Sqlite.Tests/CommunityToolkit.Aspire.Hosting.Sqlite.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.SqlServer.Extensions.Tests/CommunityToolkit.Aspire.Hosting.SqlServer.Extensions.Tests.csproj" />
Expand All @@ -250,4 +255,4 @@
<Folder Name="/tests/tests-app-hosts/">
<Project Path="tests-app-hosts/Ollama.AppHost/Ollama.AppHost.csproj" />
</Folder>
</Solution>
</Solution>
19 changes: 19 additions & 0 deletions examples/solr/CommunityToolkit.Aspire.Hosting.Solr.AppHost.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<Sdk Name="Aspire.AppHost.Sdk" Version="$(AspireAppHostSdkVersion)"/>

<PropertyGroup>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireHost>true</IsAspireHost>
<UserSecretsId>bfe6b134-1a06-4449-a146-ba3cdb0d02a6</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="../../src/CommunityToolkit.Aspire.Hosting.Solr/CommunityToolkit.Aspire.Hosting.Solr.csproj" IsAspireProjectResource="false" />
</ItemGroup>
</Project>
14 changes: 14 additions & 0 deletions examples/solr/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
var builder = DistributedApplication.CreateBuilder(args);

// Add Solr resource with default core name "solr"
var solr = builder.AddSolr("solr");

// Add Solr resource with custom port and core name
var solrWithCustomPort = builder.AddSolr("solr-custom", port: 8984, coreName: "mycore");

// Reference the Solr resources in a project (example)
// var exampleProject = builder.AddProject<Projects.ExampleProject>()
// .WithReference(solr)
// .WithReference(solrWithCustomPort);

builder.Build().Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Nullable>enable</Nullable>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<Nullable>enable</Nullable>

Controlled by the parent props files

<Description>A .NET Aspire hosting integration for Apache Solr.</Description>
<PackageTags>aspire solr search hosting</PackageTags>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<PackageTags>aspire solr search hosting</PackageTags>
<PackageTags>solr search hosting</PackageTags>

We'll append aspire automatically

<GenerateDocumentationFile>true</GenerateDocumentationFile>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<GenerateDocumentationFile>true</GenerateDocumentationFile>

Controlled by the parent props files

</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting" />
</ItemGroup>
</Project>
42 changes: 42 additions & 0 deletions src/CommunityToolkit.Aspire.Hosting.Solr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# CommunityToolkit.Aspire.Hosting.Solr

This package provides a .NET Aspire hosting integration for [Apache Solr](https://solr.apache.org/), enabling you to add and configure a Solr container as part of your distributed application.

## Getting Started

### Install the package

In your AppHost project, install the package using the following command:

```dotnetcli
dotnet add package CommunityToolkit.Aspire.Hosting.Solr
```

## Usage Example

```csharp
var builder = DistributedApplication.CreateBuilder(args);

// Add Solr resource with default settings (port 8983, core "solr")
var solr = builder.AddSolr("solr");

// Add Solr with custom port
var solrWithCustomPort = builder.AddSolr("solr-custom", port: 8984);

// Add Solr with custom core name
var solrWithCustomCore = builder.AddSolr("solr-core", coreName: "mycore");

// Add Solr with both custom port and core name
var solrCustom = builder.AddSolr("solr-full", port: 8985, coreName: "documents");

// Reference the Solr resource in a project
var exampleProject = builder.AddProject<Projects.ExampleProject>()
.WithReference(solr);

// Initialize and run the application
builder.Build().Run();
```

## Feedback & contributing

https://github.com/dotnet/aspire
62 changes: 62 additions & 0 deletions src/CommunityToolkit.Aspire.Hosting.Solr/SolrBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using Aspire.Hosting.ApplicationModel;
using CommunityToolkit.Aspire.Hosting.Solr;
using Microsoft.Extensions.DependencyInjection;

namespace Aspire.Hosting;

/// <summary>
/// Extension methods for adding and configuring a Solr resource.
/// </summary>
public static class SolrBuilderExtensions
{
/// <summary>
/// Adds an Apache Solr container resource to the distributed application.
/// </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="port">The host port for Solr.</param>
/// <param name="coreName">The name of the core to create.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{SolrResource}"/>.</returns>
public static IResourceBuilder<SolrResource> AddSolr(this IDistributedApplicationBuilder builder, [ResourceName] string name, int? port = null, string? coreName = null)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentException.ThrowIfNullOrEmpty(name);

if (string.IsNullOrEmpty(coreName))
{
coreName = "solr";
}
Comment on lines +25 to +28
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (string.IsNullOrEmpty(coreName))
{
coreName = "solr";
}
coreName ??= "solr";


var resource = new SolrResource(name, coreName);

builder.Eventing.Subscribe<ConnectionStringAvailableEvent>(resource, async (_, ct) =>
{
var connectionString = await resource.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false);

if (connectionString is null)
{
throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{resource.Name}' resource but the connection string was null.");
}
});
Comment on lines +32 to +40
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just validating some internal logic of Aspire and isn't needed. We have it elsewhere so that we can grab the connection string and push it to a client for health checks.


var solrBuilder = builder.AddResource(resource)
.WithImage(SolrContainerImageTags.Image, SolrContainerImageTags.Tag)
.WithImageRegistry(SolrContainerImageTags.Registry)
.WithHttpEndpoint(targetPort: 8983, port: port, name: SolrResource.PrimaryEndpointName)
.WithArgs("solr-precreate", coreName);

string healthCheckKey = $"{name}_check";
var endpoint = solrBuilder.Resource.GetEndpoint(SolrResource.PrimaryEndpointName);

builder.Services.AddHealthChecks()
.AddUrlGroup(options =>
{
var uri = new Uri(endpoint.Url);
options.AddUri(new Uri(uri, $"solr/{coreName}/admin/ping"), setup => setup.ExpectHttpCode(200));
}, healthCheckKey);

solrBuilder.WithHealthCheck(healthCheckKey);

return solrBuilder;
}
}
11 changes: 11 additions & 0 deletions src/CommunityToolkit.Aspire.Hosting.Solr/SolrContainerImageTags.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace CommunityToolkit.Aspire.Hosting.Solr;

internal static class SolrContainerImageTags
{
/// <summary>docker.io</summary>
public const string Registry = "docker.io";
/// <summary>solr</summary>
public const string Image = "solr";
/// <summary>9.7</summary>
public const string Tag = "9.7";
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there's no need for a custom health check, let's remove this file

Empty file.
32 changes: 32 additions & 0 deletions src/CommunityToolkit.Aspire.Hosting.Solr/SolrResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Aspire.Hosting.ApplicationModel;

namespace Aspire.Hosting.ApplicationModel;

/// <summary>
/// Represents an Apache Solr container resource.
/// </summary>
/// <param name="name">The name of the resource.</param>
/// <param name="coreName">The name of the Solr core.</param>
public class SolrResource(string name, string coreName) : ContainerResource(name), IResourceWithConnectionString
{
internal const string PrimaryEndpointName = "http";

private EndpointReference? _primaryEndpoint;

/// <summary>
/// The Solr core name.
/// </summary>
public string CoreName { get; set; } = coreName;

/// <summary>
/// Gets the primary endpoint for the Solr server.
/// </summary>
public EndpointReference PrimaryEndpoint => _primaryEndpoint ??= new(this, PrimaryEndpointName);

/// <summary>
/// Gets the connection string expression for the Solr server.
/// </summary>
public ReferenceExpression ConnectionStringExpression => ReferenceExpression.Create(
$"http://{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}/solr/{CoreName}");

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Aspire.Hosting
{
public static partial class SolrBuilderExtensions
{
public static ApplicationModel.IResourceBuilder<ApplicationModel.SolrResource> AddSolr(this IDistributedApplicationBuilder builder, string name, int? port = null, string? coreName = null) { throw null; }
}
}
namespace Aspire.Hosting.ApplicationModel
{
public partial class SolrResource : ContainerResource, IResourceWithConnectionString, IResource, IManifestExpressionProvider, IValueProvider, IValueWithReferences
{
public SolrResource(string name, string coreName) : base(default!, default) { }
public string CoreName { get { throw null; } set { } }
public ReferenceExpression ConnectionStringExpression { get { throw null; } }
public EndpointReference PrimaryEndpoint { get { throw null; } }
}
}
62 changes: 62 additions & 0 deletions tests/CommunityToolkit.Aspire.Hosting.Solr.Tests/AppHostTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Net;
using Aspire.Components.Common.Tests;
using CommunityToolkit.Aspire.Testing;

namespace CommunityToolkit.Aspire.Hosting.Solr.Tests;

[RequiresDocker]
public class AppHostTests(AspireIntegrationTestFixture<Projects.CommunityToolkit_Aspire_Hosting_Solr_AppHost> fixture) : IClassFixture<AspireIntegrationTestFixture<Projects.CommunityToolkit_Aspire_Hosting_Solr_AppHost>>
{
[Fact]
public async Task SolrResourceStartsAndRespondsOk()
{
var resourceName = "solr";
await fixture.ResourceNotificationService.WaitForResourceHealthyAsync(resourceName).WaitAsync(TimeSpan.FromMinutes(5));
var httpClient = fixture.CreateHttpClient(resourceName);

var response = await httpClient.GetAsync("/solr/");

Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}

[Fact]
public async Task SolrResourceWithCustomPortStartsAndRespondsOk()
{
var resourceName = "solr-custom";
await fixture.ResourceNotificationService.WaitForResourceHealthyAsync(resourceName).WaitAsync(TimeSpan.FromMinutes(5));
var httpClient = fixture.CreateHttpClient(resourceName);

var response = await httpClient.GetAsync("/solr/");

Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}

[Fact]
public async Task SolrCoreIsHealthy()
{
var resourceName = "solr";
await fixture.ResourceNotificationService.WaitForResourceHealthyAsync(resourceName).WaitAsync(TimeSpan.FromMinutes(5));
var httpClient = fixture.CreateHttpClient(resourceName);

// Test that the specific core admin ping endpoint works
var response = await httpClient.GetAsync("/solr/solr/admin/ping");

Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}

[Fact]
public async Task SolrCustomCoreIsHealthy()
{
var resourceName = "solr-custom";
await fixture.ResourceNotificationService.WaitForResourceHealthyAsync(resourceName).WaitAsync(TimeSpan.FromMinutes(5));
var httpClient = fixture.CreateHttpClient(resourceName);

// Test that the custom core admin ping endpoint works
var response = await httpClient.GetAsync("/solr/mycore/admin/ping");

Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<ItemGroup>
<ProjectReference Include="..\..\examples\solr\CommunityToolkit.Aspire.Hosting.Solr.AppHost.csproj" />
<ProjectReference Include="..\..\src\CommunityToolkit.Aspire.Hosting.Solr\CommunityToolkit.Aspire.Hosting.Solr.csproj" />
<ProjectReference Include="..\CommunityToolkit.Aspire.Testing\CommunityToolkit.Aspire.Testing.csproj" />
</ItemGroup>

</Project>
Loading
Loading