| File: DevTunnelHealthCheck.cs | Web Access |
| Project: src\src\Aspire.Hosting.DevTunnels\Aspire.Hosting.DevTunnels.csproj (Aspire.Hosting.DevTunnels) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Globalization; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Logging; namespace Aspire.Hosting.DevTunnels; internal sealed class DevTunnelHealthCheck( IDevTunnelClient devTunnelClient, LoggedOutNotificationManager loggedOutNotificationManager, DevTunnelResource tunnelResource, ILogger<DevTunnelHealthCheck> logger) : IHealthCheck { private readonly IDevTunnelClient _devTunnelClient = devTunnelClient ?? throw new ArgumentNullException(nameof(devTunnelClient)); private readonly LoggedOutNotificationManager _loggedOutNotificationManager = loggedOutNotificationManager ?? throw new ArgumentNullException(nameof(loggedOutNotificationManager)); private readonly DevTunnelResource _tunnelResource = tunnelResource ?? throw new ArgumentNullException(nameof(tunnelResource)); public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { try { var tunnelStatus = await _devTunnelClient.GetTunnelAsync(_tunnelResource.ResolvedTunnelId, logger, cancellationToken).ConfigureAwait(false); tunnelResource.LastKnownStatus = tunnelStatus; if (tunnelStatus.HostConnections == 0) { return HealthCheckResult.Unhealthy(string.Format(CultureInfo.CurrentCulture, Resources.MessageStrings.DevTunnelUnhealthy_NoActiveHostConnections, _tunnelResource.TunnelId)); } // Check that expected ports are active foreach (var portResource in _tunnelResource.Ports) { var tunnelPort = await portResource.GetTunnelPortAsync(cancellationToken).ConfigureAwait(false); var portStatus = tunnelStatus.Ports?.FirstOrDefault(p => p.PortNumber == tunnelPort); portResource.LastKnownStatus = portStatus; if (portStatus?.PortUri is null) { return HealthCheckResult.Unhealthy(string.Format(CultureInfo.CurrentCulture, Resources.MessageStrings.DevTunnelUnhealthy_PortInactive, _tunnelResource.TunnelId, tunnelPort)); } } // Get tunnel and port access status var tunnelAccessStatus = await _devTunnelClient.GetAccessAsync(_tunnelResource.ResolvedTunnelId, portNumber: null, logger, cancellationToken).ConfigureAwait(false); _tunnelResource.LastKnownAccessStatus = tunnelAccessStatus; // Get access status for each port foreach (var portResource in _tunnelResource.Ports) { var tunnelPort = await portResource.GetTunnelPortAsync(cancellationToken).ConfigureAwait(false); var portAccessStatus = await _devTunnelClient.GetAccessAsync(_tunnelResource.ResolvedTunnelId, tunnelPort, logger, cancellationToken).ConfigureAwait(false); portResource.LastKnownAccessStatus = portAccessStatus; } return HealthCheckResult.Healthy(string.Format(CultureInfo.CurrentCulture, Resources.MessageStrings.DevTunnelHealthy, _tunnelResource.TunnelId, tunnelStatus.HostConnections, tunnelStatus.Ports?.Count)); } catch (Exception ex) { tunnelResource.LastKnownStatus = null; try { // Check if the user is still logged in var loginStatus = await _devTunnelClient.GetUserLoginStatusAsync(logger, cancellationToken).ConfigureAwait(false); if (!loginStatus.IsLoggedIn) { _ = Task.Run(() => _loggedOutNotificationManager.NotifyUserLoggedOutAsync(cancellationToken).ConfigureAwait(false)); } } catch { } // Ignore errors from login check return HealthCheckResult.Unhealthy(string.Format(CultureInfo.CurrentCulture, Resources.MessageStrings.DevTunnelUnhealthy_Error, _tunnelResource.TunnelId, ex.Message), ex); } } }