Skip to content

Commit 7f4bea1

Browse files
authored
Make URLs available on snapshot of unstarted and waiting resources (#9123)
1 parent 2d91828 commit 7f4bea1

File tree

3 files changed

+97
-70
lines changed

3 files changed

+97
-70
lines changed

src/Aspire.Hosting/Dcp/DcpExecutor.cs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,8 @@ async Task CreateResourceExecutablesAsyncCore(IResource resource, IEnumerable<Ap
945945

946946
try
947947
{
948+
await ProcessUrls(resource, cancellationToken).ConfigureAwait(false);
949+
948950
// Publish snapshots built from DCP resources. Do this now to populate more values from DCP (URLs, source) to ensure they're
949951
// available if the resource isn't immediately started because it's waiting or is configured for explicit start.
950952
foreach (var er in executables)
@@ -1217,6 +1219,8 @@ async Task CreateContainerAsyncCore(AppResource cr, CancellationToken cancellati
12171219

12181220
foreach (var cr in containerResources)
12191221
{
1222+
await ProcessUrls(cr.ModelResource, cancellationToken).ConfigureAwait(false);
1223+
12201224
// Publish snapshot built from DCP resource. Do this now to populate more values from DCP (URLs, source) to ensure they're
12211225
// available if the resource isn't immediately started because it's waiting or is configured for explicit start.
12221226
await _executorEvents.PublishAsync(new OnResourceChangedContext(_shutdownCancellation.Token, KnownResourceTypes.Container, cr.ModelResource, cr.DcpResourceName, new ResourceStatus(null, null, null), s => _snapshotBuilder.ToSnapshot((Container)cr.DcpResource, s))).ConfigureAwait(false);
@@ -1452,6 +1456,74 @@ private async Task CreateResourcesAsync<RT>(CancellationToken cancellationToken)
14521456
}
14531457
}
14541458

1459+
private async Task ProcessUrls(IResource resource, CancellationToken cancellationToken)
1460+
{
1461+
// Call the callbacks to configure resource URLs
1462+
1463+
if (resource is not IResourceWithEndpoints resourceWithEndpoints)
1464+
{
1465+
return;
1466+
}
1467+
1468+
// Project endpoints to URLS
1469+
var urls = new List<ResourceUrlAnnotation>();
1470+
1471+
if (resource.TryGetEndpoints(out var endpoints))
1472+
{
1473+
foreach (var endpoint in endpoints)
1474+
{
1475+
// Create a URL for each endpoint
1476+
if (endpoint.AllocatedEndpoint is { } allocatedEndpoint)
1477+
{
1478+
var url = new ResourceUrlAnnotation { Url = allocatedEndpoint.UriString, Endpoint = new EndpointReference(resourceWithEndpoints, endpoint) };
1479+
urls.Add(url);
1480+
}
1481+
}
1482+
}
1483+
1484+
// Run the URL callbacks
1485+
if (resource.TryGetAnnotationsOfType<ResourceUrlsCallbackAnnotation>(out var callbacks))
1486+
{
1487+
var urlsCallbackContext = new ResourceUrlsCallbackContext(_executionContext, resource, urls, cancellationToken)
1488+
{
1489+
Logger = _loggerService.GetLogger(resource.Name)
1490+
};
1491+
foreach (var callback in callbacks)
1492+
{
1493+
await callback.Callback(urlsCallbackContext).ConfigureAwait(false);
1494+
}
1495+
}
1496+
1497+
// Clear existing URLs
1498+
if (resource.TryGetUrls(out var existingUrls))
1499+
{
1500+
var existing = existingUrls.ToArray();
1501+
for (var i = existing.Length - 1; i >= 0; i--)
1502+
{
1503+
var url = existing[i];
1504+
resource.Annotations.Remove(url);
1505+
}
1506+
}
1507+
1508+
// Convert relative endpoint URLs to absolute URLs
1509+
foreach (var url in urls)
1510+
{
1511+
if (url.Endpoint is { } endpoint)
1512+
{
1513+
if (url.Url.StartsWith('/') && endpoint.AllocatedEndpoint is { } allocatedEndpoint)
1514+
{
1515+
url.Url = allocatedEndpoint.UriString.TrimEnd('/') + url.Url;
1516+
}
1517+
}
1518+
}
1519+
1520+
// Add URLs
1521+
foreach (var url in urls)
1522+
{
1523+
resource.Annotations.Add(url);
1524+
}
1525+
}
1526+
14551527
/// <summary>
14561528
/// Create a patch update using the specified resource.
14571529
/// A copy is taken of the resource to avoid permanently changing it.

src/Aspire.Hosting/Orchestrator/ApplicationOrchestrator.cs

Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,6 @@ private async Task OnEndpointsAllocated(OnEndpointsAllocatedContext context)
104104

105105
private async Task OnResourceStarting(OnResourceStartingContext context)
106106
{
107-
// Call the callbacks to configure resource URLs
108-
await ProcessUrls(context.Resource, context.CancellationToken).ConfigureAwait(false);
109-
110107
switch (context.ResourceType)
111108
{
112109
case KnownResourceTypes.Project:
@@ -156,72 +153,6 @@ private async Task OnResourcesPrepared(OnResourcesPreparedContext _)
156153
await PublishResourcesWithInitialStateAsync().ConfigureAwait(false);
157154
}
158155

159-
private async Task ProcessUrls(IResource resource, CancellationToken cancellationToken)
160-
{
161-
if (resource is not IResourceWithEndpoints resourceWithEndpoints)
162-
{
163-
return;
164-
}
165-
166-
// Project endpoints to URLS
167-
var urls = new List<ResourceUrlAnnotation>();
168-
169-
if (resource.TryGetEndpoints(out var endpoints))
170-
{
171-
foreach (var endpoint in endpoints)
172-
{
173-
// Create a URL for each endpoint
174-
if (endpoint.AllocatedEndpoint is { } allocatedEndpoint)
175-
{
176-
var url = new ResourceUrlAnnotation { Url = allocatedEndpoint.UriString, Endpoint = new EndpointReference(resourceWithEndpoints, endpoint) };
177-
urls.Add(url);
178-
}
179-
}
180-
}
181-
182-
// Run the URL callbacks
183-
if (resource.TryGetAnnotationsOfType<ResourceUrlsCallbackAnnotation>(out var callbacks))
184-
{
185-
var urlsCallbackContext = new ResourceUrlsCallbackContext(_executionContext, resource, urls, cancellationToken)
186-
{
187-
Logger = _loggerService.GetLogger(resource.Name)
188-
};
189-
foreach (var callback in callbacks)
190-
{
191-
await callback.Callback(urlsCallbackContext).ConfigureAwait(false);
192-
}
193-
}
194-
195-
// Clear existing URLs
196-
if (resource.TryGetUrls(out var existingUrls))
197-
{
198-
var existing = existingUrls.ToArray();
199-
for (var i = existing.Length - 1; i >= 0; i--)
200-
{
201-
var url = existing[i];
202-
resource.Annotations.Remove(url);
203-
}
204-
}
205-
206-
// Convert relative endpoint URLs to absolute URLs
207-
foreach (var url in urls)
208-
{
209-
if (url.Endpoint is { } endpoint)
210-
{
211-
if (url.Url.StartsWith('/') && endpoint.AllocatedEndpoint is { } allocatedEndpoint)
212-
{
213-
url.Url = allocatedEndpoint.UriString.TrimEnd('/') + url.Url;
214-
}
215-
}
216-
}
217-
218-
// Add URLs
219-
foreach (var url in urls)
220-
{
221-
resource.Annotations.Add(url);
222-
}
223-
}
224-
225156
private Task ProcessResourcesWithoutLifetime(AfterEndpointsAllocatedEvent @event, CancellationToken cancellationToken)
226157
{
227158
async Task ProcessValueAsync(IResource resource, IValueProvider vp)

tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,19 @@ public async Task ExplicitStart_StartExecutable()
181181
Assert.Contains("TestProject.ServiceA.csproj", notStartedResourceEvent.Snapshot.Properties.Single(p => p.Name == "project.path").Value?.ToString());
182182
Assert.Contains("TestProject.ServiceB.csproj", dependentResourceEvent.Snapshot.Properties.Single(p => p.Name == "project.path").Value?.ToString());
183183

184+
Assert.Collection(notStartedResourceEvent.Snapshot.Urls, u =>
185+
{
186+
Assert.Equal("http://localhost:5156", u.Url);
187+
Assert.Equal("http", u.Name);
188+
Assert.True(u.IsInactive);
189+
});
190+
Assert.Collection(dependentResourceEvent.Snapshot.Urls, u =>
191+
{
192+
Assert.Equal("http://localhost:5254", u.Url);
193+
Assert.Equal("http", u.Name);
194+
Assert.True(u.IsInactive);
195+
});
196+
184197
logger.LogInformation("Start explicit start resource.");
185198
await orchestrator.StartResourceAsync(notStartedResourceEvent.ResourceId, CancellationToken.None).DefaultTimeout(TestConstants.LongTimeoutTimeSpan);
186199
var runningResourceEvent = await rns.WaitForResourceAsync(notStartedResourceName, e => e.Snapshot.State?.Text == KnownResourceStates.Running).DefaultTimeout(TestConstants.LongTimeoutTimeSpan);
@@ -239,6 +252,18 @@ public async Task ExplicitStart_StartContainer()
239252
var notStartedResourceEvent = await rns.WaitForResourceAsync(notStartedResourceName, e => e.Snapshot.State?.Text == KnownResourceStates.NotStarted).DefaultTimeout(TestConstants.LongTimeoutTimeSpan);
240253
var dependentResourceEvent = await rns.WaitForResourceAsync(dependentResourceName, e => e.Snapshot.State?.Text == KnownResourceStates.Waiting).DefaultTimeout(TestConstants.LongTimeoutTimeSpan);
241254

255+
Assert.Collection(notStartedResourceEvent.Snapshot.Urls, u =>
256+
{
257+
Assert.Equal("tcp://localhost:6379", u.Url);
258+
Assert.True(u.IsInactive);
259+
});
260+
Assert.Collection(dependentResourceEvent.Snapshot.Urls, u =>
261+
{
262+
Assert.Equal("http://localhost:5254", u.Url);
263+
Assert.Equal("http", u.Name);
264+
Assert.True(u.IsInactive);
265+
});
266+
242267
// Source should be populated on non-started resources.
243268
Assert.Equal(RedisImageSource, notStartedResourceEvent.Snapshot.Properties.Single(p => p.Name == "container.image").Value?.ToString());
244269
Assert.Contains("TestProject.ServiceB.csproj", dependentResourceEvent.Snapshot.Properties.Single(p => p.Name == "project.path").Value?.ToString());
@@ -249,7 +274,6 @@ public async Task ExplicitStart_StartContainer()
249274
Assert.Collection(runningResourceEvent.Snapshot.Urls, u =>
250275
{
251276
Assert.Equal("tcp://localhost:6379", u.Url);
252-
Assert.True(u.IsInactive);
253277
});
254278

255279
// Dependent resource should now run.

0 commit comments

Comments
 (0)