Last active
May 16, 2024 11:28
-
-
Save afscrome/828bac82e30f2643478325e3355a9ce0 to your computer and use it in GitHub Desktop.
Aspire Readiness checks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class DelayStartAnnotation(IResource waitForResource) : IResourceAnnotation | |
{ | |
public IResource WaitForResource { get; } = waitForResource; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Aspire.Hosting.Lifecycle; | |
using Microsoft.Extensions.Diagnostics.HealthChecks; | |
using Microsoft.Extensions.Logging; | |
using System.Collections.Immutable; | |
public class DependencyDelayStartLifecycleHook(ResourceNotificationService resourceNotificationService) : IDistributedApplicationLifecycleHook | |
{ | |
private readonly ResourceNotificationService _resourceNotificationService1 = resourceNotificationService; | |
public Task BeforeStartAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken) | |
{ | |
foreach (var resource in appModel.Resources) | |
{ | |
var waitOn = resource.Annotations | |
.OfType<DelayStartAnnotation>() | |
.Select(x => x.WaitForResource) | |
.ToArray(); | |
if (waitOn.Any()) | |
{ | |
resource.Annotations.Add(new EnvironmentCallbackAnnotation(async context => | |
{ | |
if (context.ExecutionContext.IsPublishMode) | |
{ | |
return; | |
} | |
await _resourceNotificationService1.PublishUpdateAsync(resource, x => | |
{ | |
return x with | |
{ | |
State = new( | |
"Waiting", | |
KnownResourceStateStyles.Warn.ToString() | |
) | |
}; | |
}); | |
await WaitForDependencies(waitOn, context.Logger, context.CancellationToken); | |
})); | |
} | |
} | |
return Task.CompletedTask; | |
} | |
async Task WaitForDependencies(IResource[] dependsOn, ILogger? logger, CancellationToken cancellationToken) | |
{ | |
var remainingReadinessChecks = dependsOn | |
.SelectMany(x => x.Annotations.OfType<ReadinessCheckAnnotation>()) | |
.ToImmutableList(); | |
int remainingAttempts = 30; | |
var delay = TimeSpan.FromSeconds(1); | |
logger?.LogInformation("Waiting for dependencies to be ready"); | |
while (remainingReadinessChecks.Count > 0 && remainingAttempts > 0) | |
{ | |
await Task.Delay(delay, cancellationToken); | |
foreach (var check in remainingReadinessChecks) | |
{ | |
var result = await check.HealthCheck.CheckHealthAsync(new HealthCheckContext(), cancellationToken); | |
if (result.Status == HealthStatus.Healthy) | |
{ | |
var healthCheckName = "TODO - ???pull from HealthCheckRegistration???"; | |
logger?.LogDebug("{Dependency} is ready", healthCheckName); | |
remainingReadinessChecks = remainingReadinessChecks.Remove(check); | |
} | |
} | |
remainingAttempts--; | |
} | |
if (remainingReadinessChecks.Count > 0) | |
{ | |
//TODO: describe what checks failed | |
throw new Exception("Readiness checks failed"); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.Extensions.Diagnostics.HealthChecks; | |
public class ReadinessCheckAnnotation(IHealthCheck healthCheck) : IResourceAnnotation | |
{ | |
public IHealthCheck HealthCheck { get; } = healthCheck; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.Extensions.Diagnostics.HealthChecks; | |
public static class IResourceBuilderExtensions | |
{ | |
public static IResourceBuilder<T> WithReadinessCheck<T>(this IResourceBuilder<T> builder, IHealthCheck healthCheck) | |
where T : IResource | |
=> builder.WithAnnotation(new ReadinessCheckAnnotation(healthCheck)); | |
// TODO: This is POC code that needs refactoring | |
// - Refactor to a more central service so if two or more things depend on the same | |
// dependency, they don't need to run their own copy of health checks | |
// - Improve logging | |
public static IResourceBuilder<T> WithDependencyReadinessWait<T>(this IResourceBuilder<T> builder, IResource resource) | |
where T : IResourceWithEnvironment | |
=> builder.WithAnnotation(new DelayStartAnnotation(resource)); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For an improved version, see https://github.com/davidfowl/WaitForDependenciesAspire/