9
9
using Aspire . Hosting . Publishing ;
10
10
using Aspire . Hosting . Utils ;
11
11
using Microsoft . Extensions . DependencyInjection ;
12
+ using Microsoft . Extensions . Diagnostics . HealthChecks ;
12
13
using Microsoft . Extensions . Hosting ;
13
14
using Microsoft . Extensions . Logging ;
14
15
using Microsoft . Extensions . Options ;
@@ -21,7 +22,8 @@ internal class AppHostRpcTarget(
21
22
IServiceProvider serviceProvider ,
22
23
IDistributedApplicationEventing eventing ,
23
24
PublishingActivityProgressReporter activityReporter ,
24
- IHostApplicationLifetime lifetime
25
+ IHostApplicationLifetime lifetime ,
26
+ DistributedApplicationOptions options
25
27
)
26
28
{
27
29
public async IAsyncEnumerable < ( string Id , string StatusText , bool IsComplete , bool IsError ) > GetPublishingActivitiesAsync ( [ EnumeratorCancellation ] CancellationToken cancellationToken )
@@ -101,6 +103,29 @@ public Task<long> PingAsync(long timestamp, CancellationToken cancellationToken)
101
103
102
104
public Task < ( string BaseUrlWithLoginToken , string ? CodespacesUrlWithLoginToken ) > GetDashboardUrlsAsync ( )
103
105
{
106
+ return GetDashboardUrlsAsync ( CancellationToken . None ) ;
107
+ }
108
+
109
+ public async Task < ( string BaseUrlWithLoginToken , string ? CodespacesUrlWithLoginToken ) > GetDashboardUrlsAsync ( CancellationToken cancellationToken )
110
+ {
111
+ if ( ! options . DashboardEnabled )
112
+ {
113
+ logger . LogError ( "Dashboard URL requested but dashboard is disabled." ) ;
114
+ throw new InvalidOperationException ( "Dashboard URL requested but dashboard is disabled." ) ;
115
+ }
116
+
117
+ // Wait for the dashboard to be healthy before returning the URL. This next statement has several
118
+ // layers of hacks. Some to work around devcontainer/codespaces port forwarding behavior, and one to
119
+ // temporarily work around the fact that resource events abuse the state to mark the resource as
120
+ // hidden instead of having another field. There is a corresponding modification in the ResourceHealthService
121
+ // which allows the dashboard resource to trigger health reports even though it never enters
122
+ // the Running state. This is a hack. The reason we can't just check HealthStatus is because
123
+ // the current implementation of HealthStatus depends on the state of the resource as well.
124
+ await resourceNotificationService . WaitForResourceAsync (
125
+ KnownResourceNames . AspireDashboard ,
126
+ re => re . Snapshot . HealthReports . All ( h => h . Status == HealthStatus . Healthy ) ,
127
+ cancellationToken ) . ConfigureAwait ( false ) ;
128
+
104
129
var dashboardOptions = serviceProvider . GetService < IOptions < DashboardOptions > > ( ) ;
105
130
106
131
if ( dashboardOptions is null )
@@ -122,11 +147,11 @@ public Task<long> PingAsync(long timestamp, CancellationToken cancellationToken)
122
147
123
148
if ( baseUrlWithLoginToken == codespacesUrlWithLoginToken )
124
149
{
125
- return Task . FromResult < ( string , string ? ) > ( ( baseUrlWithLoginToken , null ) ) ;
150
+ return ( baseUrlWithLoginToken , null ) ;
126
151
}
127
152
else
128
153
{
129
- return Task . FromResult ( ( baseUrlWithLoginToken , codespacesUrlWithLoginToken ) ) ;
154
+ return ( baseUrlWithLoginToken , codespacesUrlWithLoginToken ) ;
130
155
}
131
156
}
132
157
0 commit comments