-
Notifications
You must be signed in to change notification settings - Fork 235
/
Copy pathHealthChecksUIResource.cs
164 lines (136 loc) · 7.45 KB
/
HealthChecksUIResource.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
using System.Diagnostics;
using Aspire.Hosting.Lifecycle;
namespace HealthChecksUI;
/// <summary>
/// A container-based resource for the HealthChecksUI container.
/// See https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks?tab=readme-ov-file#HealthCheckUI
/// </summary>
/// <param name="name">The resource name.</param>
public class HealthChecksUIResource(string name) : ContainerResource(name), IResourceWithServiceDiscovery
{
/// <summary>
/// The projects to be monitored by the HealthChecksUI container.
/// </summary>
public IList<MonitoredProject> MonitoredProjects { get; } = [];
/// <summary>
/// Known environment variables for the HealthChecksUI container that can be used to configure the container.
/// Taken from https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/blob/master/doc/ui-docker.md#environment-variables-table
/// </summary>
public static class KnownEnvVars
{
public const string UiPath = "ui_path";
// These keys are taken from https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks?tab=readme-ov-file#sample-2-configuration-using-appsettingsjson
public const string HealthChecksConfigSection = "HealthChecksUI__HealthChecks";
public const string HealthCheckName = "Name";
public const string HealthCheckUri = "Uri";
internal static string GetHealthCheckNameKey(int index) => $"{HealthChecksConfigSection}__{index}__{HealthCheckName}";
internal static string GetHealthCheckUriKey(int index) => $"{HealthChecksConfigSection}__{index}__{HealthCheckUri}";
}
}
/// <summary>
/// Represents a project to be monitored by a <see cref="HealthChecksUIResource"/>.
/// </summary>
public class MonitoredProject(IResourceBuilder<ProjectResource> project, string endpointName, string probePath)
{
private string? _name;
/// <summary>
/// The project to be monitored.
/// </summary>
public IResourceBuilder<ProjectResource> Project { get; } = project ?? throw new ArgumentNullException(nameof(project));
/// <summary>
/// The name of the endpoint the project serves health check details from. If it doesn't exist it will be added.
/// </summary>
public string EndpointName { get; } = endpointName ?? throw new ArgumentNullException(nameof(endpointName));
/// <summary>
/// The name of the project to be displayed in the HealthChecksUI dashboard. Defaults to the project resource's name.
/// </summary>
public string Name
{
get => _name ?? Project.Resource.Name;
set { _name = value; }
}
/// <summary>
/// The request path the project serves health check details for the HealthChecksUI dashboard from.
/// </summary>
public string ProbePath { get; set; } = probePath ?? throw new ArgumentNullException(nameof(probePath));
}
internal class HealthChecksUILifecycleHook(DistributedApplicationExecutionContext executionContext) : IDistributedApplicationLifecycleHook
{
private const string HEALTHCHECKSUI_URLS = "HEALTHCHECKSUI_URLS";
public Task BeforeStartAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
{
// Configure each project referenced by a Health Checks UI resource
var healthChecksUIResources = appModel.Resources.OfType<HealthChecksUIResource>();
foreach (var healthChecksUIResource in healthChecksUIResources)
{
foreach (var monitoredProject in healthChecksUIResource.MonitoredProjects)
{
var project = monitoredProject.Project;
// Add the health check endpoint if it doesn't exist
var healthChecksEndpoint = project.GetEndpoint(monitoredProject.EndpointName);
if (!healthChecksEndpoint.Exists)
{
project.WithHttpEndpoint(name: monitoredProject.EndpointName);
Debug.Assert(healthChecksEndpoint.Exists, "The health check endpoint should exist after adding it.");
}
// Set environment variable to configure the URLs the health check endpoint is accessible from
project.WithEnvironment(context =>
{
var probePath = monitoredProject.ProbePath.TrimStart('/');
var healthChecksEndpointsExpression = ReferenceExpression.Create($"{healthChecksEndpoint}/{probePath}");
if (context.ExecutionContext.IsRunMode)
{
// Running during dev inner-loop
var containerHost = healthChecksUIResource.GetEndpoint("http").ContainerHost;
var fromContainerUriBuilder = new UriBuilder(healthChecksEndpoint.Url)
{
Host = containerHost,
Path = monitoredProject.ProbePath
};
healthChecksEndpointsExpression = ReferenceExpression.Create($"{healthChecksEndpointsExpression};{fromContainerUriBuilder.ToString()}");
}
context.EnvironmentVariables.Add(HEALTHCHECKSUI_URLS, healthChecksEndpointsExpression);
});
}
}
if (executionContext.IsPublishMode)
{
ConfigureHealthChecksUIContainers(appModel.Resources, isPublishing: true);
}
return Task.CompletedTask;
}
public Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
{
ConfigureHealthChecksUIContainers(appModel.Resources, isPublishing: false);
return Task.CompletedTask;
}
private static void ConfigureHealthChecksUIContainers(IResourceCollection resources, bool isPublishing)
{
var healhChecksUIResources = resources.OfType<HealthChecksUIResource>();
foreach (var healthChecksUIResource in healhChecksUIResources)
{
var monitoredProjects = healthChecksUIResource.MonitoredProjects;
// Add environment variables to configure the HealthChecksUI container with the health checks endpoints of each referenced project
// See example configuration at https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks?tab=readme-ov-file#sample-2-configuration-using-appsettingsjson
for (var i = 0; i < monitoredProjects.Count; i++)
{
var monitoredProject = monitoredProjects[i];
var healthChecksEndpoint = monitoredProject.Project.GetEndpoint(monitoredProject.EndpointName);
// Set health check name
var nameEnvVarName = HealthChecksUIResource.KnownEnvVars.GetHealthCheckNameKey(i);
healthChecksUIResource.Annotations.Add(
new EnvironmentCallbackAnnotation(
nameEnvVarName,
() => monitoredProject.Name));
// Set health check URL
var probePath = monitoredProject.ProbePath.TrimStart('/');
var urlEnvVarName = HealthChecksUIResource.KnownEnvVars.GetHealthCheckUriKey(i);
healthChecksUIResource.Annotations.Add(
new EnvironmentCallbackAnnotation(
context => context[urlEnvVarName] = isPublishing
? ReferenceExpression.Create($"{healthChecksEndpoint}/{probePath}")
: new HostUrl($"{healthChecksEndpoint.Url}/{probePath}")));
}
}
}
}