diff --git a/Directory.Build.props b/Directory.Build.props
index feef18c9263..86d123d0c29 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -7,6 +7,7 @@
preview
MIT
$(MSBuildThisFileDirectory)/src/Shared/
+ $(MSBuildThisFileDirectory)/src/Components/
$([MSBuild]::NormalizeDirectory($(MSBuildThisFileDirectory), 'tests', 'Shared'))
$([MSBuild]::NormalizeDirectory($(TestsSharedDir), 'RepoTesting'))
$(MSBuildThisFileDirectory)/src/Vendoring/
diff --git a/playground/Elasticsearch/Elasticsearch.AppHost/Program.cs b/playground/Elasticsearch/Elasticsearch.AppHost/Program.cs
index 82f1391e383..53d1e4eac7a 100644
--- a/playground/Elasticsearch/Elasticsearch.AppHost/Program.cs
+++ b/playground/Elasticsearch/Elasticsearch.AppHost/Program.cs
@@ -7,7 +7,8 @@
.WithDataVolume();
builder.AddProject("elasticsearch-apiservice")
- .WithReference(elasticsearch);
+ .WithReference(elasticsearch)
+ .WaitFor(elasticsearch);
#if !SKIP_DASHBOARD_REFERENCE
// This project is only added in playground projects to support development/debugging
diff --git a/spelling.dic b/spelling.dic
index 5c6a474ea4b..a19ed0d1398 100644
--- a/spelling.dic
+++ b/spelling.dic
@@ -63,3 +63,4 @@ uris
urls
kubernetes
Pgweb
+elasticsearch
diff --git a/src/Aspire.Hosting.Elasticsearch/Aspire.Hosting.Elasticsearch.csproj b/src/Aspire.Hosting.Elasticsearch/Aspire.Hosting.Elasticsearch.csproj
index 4f18a0ec493..bc35cd8223e 100644
--- a/src/Aspire.Hosting.Elasticsearch/Aspire.Hosting.Elasticsearch.csproj
+++ b/src/Aspire.Hosting.Elasticsearch/Aspire.Hosting.Elasticsearch.csproj
@@ -15,10 +15,12 @@
+
+
diff --git a/src/Aspire.Hosting.Elasticsearch/ElasticsearchBuilderExtensions.cs b/src/Aspire.Hosting.Elasticsearch/ElasticsearchBuilderExtensions.cs
index c746ffdeaf2..0bd9f42d281 100644
--- a/src/Aspire.Hosting.Elasticsearch/ElasticsearchBuilderExtensions.cs
+++ b/src/Aspire.Hosting.Elasticsearch/ElasticsearchBuilderExtensions.cs
@@ -1,9 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using Aspire.Elastic.Clients.Elasticsearch;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Elasticsearch;
using Aspire.Hosting.Utils;
+using Elastic.Clients.Elasticsearch;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
namespace Aspire.Hosting;
@@ -19,7 +23,7 @@ public static class ElasticsearchBuilderExtensions
/// Adds an Elasticsearch container resource to the application model.
///
///
- /// The default image is "elasticsearch" and the tag is "8.14.0".
+ /// The default image is "elasticsearch" and the tag is "8.15.1".
///
/// The .
/// The name of the resource. This name will be used as the connection string name when referenced in a dependency.
@@ -51,6 +55,30 @@ public static IResourceBuilder AddElasticsearch(
var elasticsearch = new ElasticsearchResource(name, passwordParameter);
+ string? connectionString = null;
+ ElasticsearchClient? elasticsearchClient = null;
+
+ builder.Eventing.Subscribe(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.");
+ }
+ elasticsearchClient = new ElasticsearchClient(new Uri(connectionString));
+ });
+
+ var healthCheckKey = $"{name}_check";
+ // todo: Use health check from AspNetCore.Diagnostics.HealthChecks once following PR released:
+ // https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/pull/2244
+ builder.Services.AddHealthChecks()
+ .Add(new HealthCheckRegistration(
+ healthCheckKey,
+ sp => new ElasticsearchHealthCheck(elasticsearchClient!),
+ failureStatus: default,
+ tags: default,
+ timeout: default));
+
return builder.AddResource(elasticsearch)
.WithImage(ElasticsearchContainerImageTags.Image, ElasticsearchContainerImageTags.Tag)
.WithImageRegistry(ElasticsearchContainerImageTags.Registry)
@@ -61,7 +89,8 @@ public static IResourceBuilder AddElasticsearch(
.WithEnvironment(context =>
{
context.EnvironmentVariables["ELASTIC_PASSWORD"] = elasticsearch.PasswordParameter;
- });
+ })
+ .WithHealthCheck(healthCheckKey);
}
///
diff --git a/tests/Aspire.Hosting.Elasticsearch.Tests/ElasticsearchFunctionalTests.cs b/tests/Aspire.Hosting.Elasticsearch.Tests/ElasticsearchFunctionalTests.cs
index 4a84bb9ecb9..56c2ab8900a 100644
--- a/tests/Aspire.Hosting.Elasticsearch.Tests/ElasticsearchFunctionalTests.cs
+++ b/tests/Aspire.Hosting.Elasticsearch.Tests/ElasticsearchFunctionalTests.cs
@@ -2,9 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.
using Aspire.Components.Common.Tests;
+using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Utils;
using Elastic.Clients.Elasticsearch;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Hosting;
using Polly;
using Xunit;
@@ -199,6 +201,46 @@ await pipeline.ExecuteAsync(
}
}
+ [Fact]
+ [RequiresDocker]
+ public async Task VerifyWaitForOnElasticsearchBlocksDependentResources()
+ {
+ var cts = new CancellationTokenSource(TimeSpan.FromMinutes(10));
+ using var builder = TestDistributedApplicationBuilder.CreateWithTestContainerRegistry(testOutputHelper);
+
+ var healthCheckTcs = new TaskCompletionSource();
+ builder.Services.AddHealthChecks().AddAsyncCheck("blocking_check", () =>
+ {
+ return healthCheckTcs.Task;
+ });
+
+ var resource = builder.AddElasticsearch("resource")
+ .WithHealthCheck("blocking_check");
+
+ var dependentResource = builder.AddElasticsearch("dependentresource")
+ .WaitFor(resource);
+
+ using var app = builder.Build();
+
+ var pendingStart = app.StartAsync(cts.Token);
+
+ var rns = app.Services.GetRequiredService();
+
+ await rns.WaitForResourceAsync(resource.Resource.Name, KnownResourceStates.Running, cts.Token);
+
+ await rns.WaitForResourceAsync(dependentResource.Resource.Name, KnownResourceStates.Waiting, cts.Token);
+
+ healthCheckTcs.SetResult(HealthCheckResult.Healthy());
+
+ await rns.WaitForResourceAsync(resource.Resource.Name, (re => re.Snapshot.HealthStatus == Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Healthy), cts.Token);
+
+ await rns.WaitForResourceAsync(dependentResource.Resource.Name, KnownResourceStates.Running, cts.Token);
+
+ await pendingStart;
+
+ await app.StopAsync();
+ }
+
private static async Task CreateTestData(ElasticsearchClient elasticsearchClient, ITestOutputHelper testOutputHelper, CancellationToken cancellationToken)
{
var indexResponse = await elasticsearchClient.IndexAsync(s_person, IndexName, s_person.Id, cancellationToken);
diff --git a/tests/helix/send-to-helix-basictests.targets b/tests/helix/send-to-helix-basictests.targets
index ebbc6a5c1d7..986eec363bb 100644
--- a/tests/helix/send-to-helix-basictests.targets
+++ b/tests/helix/send-to-helix-basictests.targets
@@ -32,7 +32,7 @@
<_DefaultWorkItems TimeoutMs="900000" />
- <_DefaultWorkItems Condition="'%(FileName)' == 'Aspire.Hosting.Elasticsearch.Tests'" TimeoutMs="1200000" />
+ <_DefaultWorkItems Condition="'%(FileName)' == 'Aspire.Hosting.Elasticsearch.Tests'" TimeoutMs="3600000" />
<_DefaultWorkItems Condition="'%(FileName)' == 'Aspire.Hosting.Oracle.Tests'" TimeoutMs="1200000" />
<_DefaultWorkItems Condition="'%(FileName)' == 'Aspire.Pomelo.EntityFrameworkCore.MySql.Tests'" TimeoutMs="1200000" />