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

Switch to using Aspire #1784

Merged
merged 29 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a92663d
Working on migrating to aspire
ejsmith Oct 26, 2024
bcaaac7
Progress
ejsmith Oct 27, 2024
ac9b8df
Some aspire progress
ejsmith Oct 31, 2024
b33b4a0
Merge branch 'main' into aspire9
ejsmith Oct 31, 2024
1247d57
Fix bad merge
ejsmith Oct 31, 2024
4892f5f
Update to Aspire 9
ejsmith Nov 13, 2024
3655fc6
Merge branch 'main' into aspire9
ejsmith Nov 14, 2024
10ed60c
Fix duplicate project refs
ejsmith Nov 14, 2024
dd4d05e
Merge branch 'main' into aspire9
niemyjski Nov 28, 2024
c2a626f
Update elasticsearch to 8.16.1
ejsmith Dec 3, 2024
95561a5
Merge branch 'main' into aspire9
ejsmith Dec 6, 2024
83a5df3
Add storage to Aspire
ejsmith Dec 6, 2024
a537251
Merge branch 'main' into aspire9
ejsmith Dec 16, 2024
4794fc8
Update Elasticsearch
ejsmith Dec 16, 2024
213332f
Fix tests
ejsmith Dec 17, 2024
ba933e5
Revert some changes. Fix linting.
ejsmith Dec 17, 2024
a7f3c46
Revert more changes
ejsmith Dec 17, 2024
f91004d
Cleanup
ejsmith Dec 17, 2024
2db11fb
Use the right Elasticsearch docker image
ejsmith Dec 17, 2024
2280e3e
Use explicit minio version
ejsmith Dec 17, 2024
fc66183
Fixed launch setting
niemyjski Dec 17, 2024
bf13d0e
Removed start and stop services
niemyjski Dec 17, 2024
b41f15f
Use fixed web client ports
ejsmith Dec 18, 2024
b4e714d
Use S3 storage when running local
ejsmith Dec 18, 2024
33171cf
Fixed an issue where code could throw due to CurrentUser
niemyjski Dec 18, 2024
93caf83
Fix S3
ejsmith Dec 18, 2024
e6d7709
[BREAKING] Remove scope prefix from bucket names and instead use scop…
niemyjski Dec 18, 2024
757ec52
Only poll queue metrics in the same process that is running the stack…
ejsmith Dec 18, 2024
d47c729
Reverted some of the breaking changes around storage.
niemyjski Dec 18, 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
1 change: 0 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ COPY ./*.sln ./NuGet.Config ./
COPY ./src/*.props ./src/
COPY ./tests/*.props ./tests/
COPY ./build/packages/* ./build/packages/
COPY ./docker/docker-compose.dcproj ./docker/

# Copy the main source project files
COPY src/*/*.csproj ./
Expand Down
13 changes: 6 additions & 7 deletions Exceptionless.sln
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.github\workflows\build.yaml = .github\workflows\build.yaml
CONTRIBUTING.md = CONTRIBUTING.md
src\Directory.Build.props = src\Directory.Build.props
docker\docker-compose.yml = docker\docker-compose.yml
Dockerfile = Dockerfile
exceptionless.http = exceptionless.http
global.json = global.json
Expand All @@ -28,8 +27,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Exceptionless.Tests", "test
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Exceptionless.Job", "src\Exceptionless.Job\Exceptionless.Job.csproj", "{788BA00C-FFBE-42A9-92A3-89E24FC137B5}"
EndProject
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker\docker-compose.dcproj", "{9F933018-9E8B-4649-8C9A-D217B5E1C184}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "http", "http", "{97ED03A0-8C49-4B15-8D93-C56AF4DDC30F}"
ProjectSection(SolutionItems) = preProject
tests\http\admin.http = tests\http\admin.http
Expand All @@ -44,6 +41,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "http", "http", "{97ED03A0-8
tests\http\webhooks.http = tests\http\webhooks.http
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Exceptionless.AppHost", "src\Exceptionless.AppHost\Exceptionless.AppHost.csproj", "{EB1AF004-A00D-4016-BA97-5E89177B0074}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -70,10 +69,10 @@ Global
{788BA00C-FFBE-42A9-92A3-89E24FC137B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{788BA00C-FFBE-42A9-92A3-89E24FC137B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{788BA00C-FFBE-42A9-92A3-89E24FC137B5}.Release|Any CPU.Build.0 = Release|Any CPU
{9F933018-9E8B-4649-8C9A-D217B5E1C184}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9F933018-9E8B-4649-8C9A-D217B5E1C184}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9F933018-9E8B-4649-8C9A-D217B5E1C184}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9F933018-9E8B-4649-8C9A-D217B5E1C184}.Release|Any CPU.Build.0 = Release|Any CPU
{EB1AF004-A00D-4016-BA97-5E89177B0074}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EB1AF004-A00D-4016-BA97-5E89177B0074}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB1AF004-A00D-4016-BA97-5E89177B0074}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB1AF004-A00D-4016-BA97-5E89177B0074}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
11 changes: 0 additions & 11 deletions docker/docker-compose.dcproj

This file was deleted.

27 changes: 27 additions & 0 deletions src/Exceptionless.AppHost/Exceptionless.AppHost.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<Sdk Name="Aspire.AppHost.Sdk" Version="9.0.0" />

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireHost>true</IsAspireHost>
<UserSecretsId>a9c2ddcc-e51d-4cd1-9782-96e1d74eec87</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.0.0" />
<PackageReference Include="Aspire.Hosting.NodeJs" Version="9.0.0" />
<PackageReference Include="Aspire.Hosting.Redis" Version="9.0.0" />
<PackageReference Include="AspNetCore.HealthChecks.Elasticsearch" Version="8.0.1" />
<PackageReference Include="Foundatio.AWS" Version="11.0.6" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Exceptionless.Job\Exceptionless.Job.csproj" />
<ProjectReference Include="..\Exceptionless.Web\Exceptionless.Web.csproj" />
</ItemGroup>

</Project>
121 changes: 121 additions & 0 deletions src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using Aspire.Hosting.Lifecycle;
using Aspire.Hosting.Utils;
using HealthChecks.Elasticsearch;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;

namespace Aspire.Hosting;

/// <summary>
/// Provides extension methods for adding Elasticsearch resources to the application model.
/// </summary>
public static class ElasticsearchBuilderExtensions
{
private const int ElasticsearchPort = 9200;
private const int ElasticsearchInternalPort = 9300;
private const int KibanaPort = 5601;

/// <summary>
/// Adds a Elasticsearch container to the application model. The default image is "docker.elastic.co/elasticsearch/elasticsearch". This version the package defaults to the 8.17.0 tag of the Elasticsearch container image
/// </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 to bind the underlying container to.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<ElasticsearchResource> AddElasticsearch(this IDistributedApplicationBuilder builder, [ResourceName] string name, int? port = null)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(name);

var elasticsearch = new ElasticsearchResource(name);

string? connectionString = null;
ElasticsearchOptions? options = null;

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

options = new ElasticsearchOptions();
options.UseServer(connectionString);
});

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

return builder.AddResource(elasticsearch)
.WithImage(ElasticsearchContainerImageTags.Image, ElasticsearchContainerImageTags.Tag)
.WithImageRegistry(ElasticsearchContainerImageTags.Registry)
.WithHttpEndpoint(targetPort: ElasticsearchPort, port: port, name: ElasticsearchResource.PrimaryEndpointName)
.WithEndpoint(targetPort: ElasticsearchInternalPort, name: ElasticsearchResource.InternalEndpointName)
.WithEnvironment("discovery.type", "single-node")
.WithEnvironment("xpack.security.enabled", "false")
.WithEnvironment("action.destructive_requires_name", "false")
.WithEnvironment("ES_JAVA_OPTS", "-Xms1g -Xmx1g")
.WithHealthCheck(healthCheckKey)
.PublishAsConnectionString();
}

public static IResourceBuilder<ElasticsearchResource> WithKibana(this IResourceBuilder<ElasticsearchResource> builder, Action<IResourceBuilder<KibanaResource>>? configureContainer = null, string? containerName = null)
{
ArgumentNullException.ThrowIfNull(builder);

if (builder.ApplicationBuilder.Resources.OfType<KibanaResource>().SingleOrDefault() is { } existingKibanaResource)
{
var builderForExistingResource = builder.ApplicationBuilder.CreateResourceBuilder(existingKibanaResource);
configureContainer?.Invoke(builderForExistingResource);
return builder;
}
else
{
containerName ??= $"{builder.Resource.Name}-kibana";

builder.ApplicationBuilder.Services.TryAddLifecycleHook<KibanaConfigWriterHook>();

var resource = new KibanaResource(containerName);
var resourceBuilder = builder.ApplicationBuilder.AddResource(resource)
.WithImage(ElasticsearchContainerImageTags.KibanaImage, ElasticsearchContainerImageTags.Tag)
.WithImageRegistry(ElasticsearchContainerImageTags.Registry)
.WithHttpEndpoint(targetPort: KibanaPort, name: containerName)
.WithEnvironment("xpack.security.enabled", "false")
.ExcludeFromManifest();

configureContainer?.Invoke(resourceBuilder);

return builder;
}
}

public static IResourceBuilder<ElasticsearchResource> WithDataVolume(this IResourceBuilder<ElasticsearchResource> builder, string? name = null)
{
ArgumentNullException.ThrowIfNull(builder);

return builder.WithVolume(name ?? VolumeNameGenerator.CreateVolumeName(builder, "data"), "/usr/share/elasticsearch/data");
}

public static IResourceBuilder<ElasticsearchResource> WithDataBindMount(this IResourceBuilder<ElasticsearchResource> builder, string source)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(source);

return builder.WithBindMount(source, "/usr/share/elasticsearch/data");
}
}

internal static class ElasticsearchContainerImageTags
{
public const string Registry = "docker.elastic.co";
public const string Image = "elasticsearch/elasticsearch";
ejsmith marked this conversation as resolved.
Show resolved Hide resolved
public const string KibanaImage = "kibana/kibana";
public const string Tag = "8.17.0";
}
72 changes: 72 additions & 0 deletions src/Exceptionless.AppHost/Extensions/ElasticsearchResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
namespace Aspire.Hosting;

/// <summary>
/// A resource that represents a Elasticsearch resource independent of the hosting model.
/// </summary>
public class ElasticsearchResource : ContainerResource, IResourceWithConnectionString
{
// this endpoint is used for all API calls over HTTP.
// This includes search and aggregations, monitoring and anything else that uses a HTTP request.
// All client libraries will use this port to talk to Elasticsearch
internal const string PrimaryEndpointName = "http";

//this endpoint is a custom binary protocol used for communications between nodes in a cluster.
//For things like cluster updates, master elections, nodes joining/leaving, shard allocation
internal const string InternalEndpointName = "internal";

/// <param name="name">The name of the resource.</param>
public ElasticsearchResource(string name) : base(name)
{
}

private EndpointReference? _primaryEndpoint;
private EndpointReference? _internalEndpoint;

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

/// <summary>
/// Gets the internal endpoint for the Elasticsearch. This endpoint used for communications between nodes in a cluster
/// </summary>
public EndpointReference InternalEndpoint => _internalEndpoint ??= new(this, InternalEndpointName);

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


/// <summary>
/// Gets the connection string expression for the Elasticsearch server for the manifest.
/// </summary>
public ReferenceExpression ConnectionStringExpression
{
get
{
if (this.TryGetLastAnnotation<ConnectionStringRedirectAnnotation>(out var connectionStringAnnotation))
{
return connectionStringAnnotation.Resource.ConnectionStringExpression;
}

return ConnectionString;
}
}

/// <summary>
/// Gets the connection string for the Elasticsearch server.
/// </summary>
/// <param name="cancellationToken"> A <see cref="CancellationToken"/> to observe while waiting for the task to complete.</param>
/// <returns>A connection string for the Elasticsearch server in the form "http://host:port".</returns>
public ValueTask<string?> GetConnectionStringAsync(CancellationToken cancellationToken = default)
{
if (this.TryGetLastAnnotation<ConnectionStringRedirectAnnotation>(out var connectionStringAnnotation))
{
return connectionStringAnnotation.Resource.GetConnectionStringAsync(cancellationToken);
}

return ConnectionString.GetValueAsync(cancellationToken);
}
}
37 changes: 37 additions & 0 deletions src/Exceptionless.AppHost/Extensions/KibanaConfigWriterHook.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Text;
using Aspire.Hosting.Lifecycle;
using Microsoft.Extensions.DependencyInjection;

namespace Aspire.Hosting;

internal class KibanaConfigWriterHook : IDistributedApplicationLifecycleHook
{
public async Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken)
{
if (appModel.Resources.OfType<KibanaResource>().SingleOrDefault() is not { } kibanaResource)
return;

var elasticsearchInstances = appModel.Resources.OfType<ElasticsearchResource>();

if (!elasticsearchInstances.Any())
return;

var hostsVariableBuilder = new StringBuilder();

foreach (var elasticsearchInstance in elasticsearchInstances)
{
if (elasticsearchInstance.PrimaryEndpoint.IsAllocated)
{
var connectionString = await elasticsearchInstance.GetConnectionStringAsync();
if (hostsVariableBuilder.Length > 0)
hostsVariableBuilder.Append(",");
hostsVariableBuilder.Append(elasticsearchInstance.PrimaryEndpoint.Scheme).Append("://").Append(elasticsearchInstance.PrimaryEndpoint.ContainerHost).Append(":").Append(elasticsearchInstance.PrimaryEndpoint.Port);
}
}

kibanaResource.Annotations.Add(new EnvironmentCallbackAnnotation(context =>
{
context.EnvironmentVariables.Add("ELASTICSEARCH_HOSTS", hostsVariableBuilder.ToString());
}));
}
}
9 changes: 9 additions & 0 deletions src/Exceptionless.AppHost/Extensions/KibanaResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Aspire.Hosting;

/// <summary>
/// A resource that represents a Kibana container.
/// </summary>
/// <param name="name">The name of the resource.</param>
public class KibanaResource(string name) : ContainerResource(name)
{
}
Loading
Loading