Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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>
<Description>A .NET Aspire hosting integration for Apache Solr.</Description>
<PackageTags>aspire solr search hosting</PackageTags>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</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
64 changes: 64 additions & 0 deletions src/CommunityToolkit.Aspire.Hosting.Solr/SolrBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
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";
}

var resource = new SolrResource(name, coreName);

string? connectionString = null;

builder.Eventing.Subscribe<ConnectionStringAvailableEvent>(resource, async (_, ct) =>
{
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.");
}
});

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";
}
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