Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions src/Aspire.Hosting/Dcp/DcpExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -779,11 +779,18 @@ private async Task CreateContainersAndExecutablesAsync(CancellationToken cancell

await _executorEvents.PublishAsync(new OnEndpointsAllocatedContext(cancellationToken)).ConfigureAwait(false);

var allocatedResources = new HashSet<string>(StringComparer.Ordinal);

// Fire the endpoints allocated event for all DCP managed resources with endpoints.
foreach (var resource in toCreate.Select(r => r.ModelResource).OfType<IResourceWithEndpoints>())
{
var resourceEvent = new ResourceEndpointsAllocatedEvent(resource, _executionContext.ServiceProvider);
await _distributedApplicationEventing.PublishAsync(resourceEvent, EventDispatchBehavior.NonBlockingConcurrent, cancellationToken).ConfigureAwait(false);
// Ensure we fire the event only once for each app model resource. There may be multiple physical replicas of
// the same app model resource which can result in the event being fired multiple times.
if (allocatedResources.Add(resource.Name))
{
var resourceEvent = new ResourceEndpointsAllocatedEvent(resource, _executionContext.ServiceProvider);
await _distributedApplicationEventing.PublishAsync(resourceEvent, EventDispatchBehavior.NonBlockingConcurrent, cancellationToken).ConfigureAwait(false);
}
}

var containersTask = CreateContainersAsync(toCreate.Where(ar => ar.DcpResource is Container), cancellationToken);
Expand Down
71 changes: 44 additions & 27 deletions src/Aspire.Hosting/Orchestrator/ApplicationOrchestrator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,12 @@ private async Task OnResourcesPrepared(OnResourcesPreparedContext context)
private async Task ProcessResourceUrlCallbacks(IResource resource, CancellationToken cancellationToken)
{
var urls = new List<ResourceUrlAnnotation>();
var existingUrls = new List<ResourceUrlAnnotation>();

if (resource.TryGetUrls(out var existingAnnotations))
{
existingUrls.AddRange(existingAnnotations);
}

// Project endpoints to URLs
if (resource.TryGetEndpoints(out var endpoints) && resource is IResourceWithEndpoints resourceWithEndpoints)
Expand All @@ -212,33 +218,55 @@ private async Task ProcessResourceUrlCallbacks(IResource resource, CancellationT
Debug.Assert(endpoint.AllocatedEndpoint is not null, "Endpoint should be allocated at this point as we're calling this from ResourceEndpointsAllocatedEvent handler.");
if (endpoint.AllocatedEndpoint is { } allocatedEndpoint)
{
// The allocated endpoint is used for service discovery and is the primary URL displayed to
// the user. In general, if valid for a particular service binding, the allocated endpoint
// will be "localhost" as that's a valid address for the .NET developer certificate. However,
// if a service is bound to a specific IP address, the allocated endpoint will be that same IP
// address.
var endpointReference = new EndpointReference(resourceWithEndpoints, endpoint);
var url = new ResourceUrlAnnotation { Url = allocatedEndpoint.UriString, Endpoint = endpointReference };
urls.Add(url);
if (allocatedEndpoint.BindingMode != EndpointBindingMode.SingleAddress && (endpoint.TargetHost is not "localhost" or "127.0.0.1"))

if (!existingUrls.Any(existingUrl => existingUrl.Url.Equals(url.Url, StringComparison.OrdinalIgnoreCase) && existingUrl.Endpoint?.EndpointName == url.Endpoint?.EndpointName))
{
// Endpoint is listening on multiple addresses so add another URL based on the declared target hostname
// For endpoints targeting all external addresses (IPv4 0.0.0.0 or IPv6 ::) use the machine name
var address = endpoint.TargetHost is "0.0.0.0" or "::" ? Environment.MachineName : endpoint.TargetHost;
url = new ResourceUrlAnnotation { Url = $"{allocatedEndpoint.UriScheme}://{address}:{allocatedEndpoint.Port}", Endpoint = endpointReference };
urls.Add(url);
}
else if (endpoint.TargetHost.Length > 10 && endpoint.TargetHost.EndsWith(".localhost", StringComparison.OrdinalIgnoreCase))

// In the case that a service is bound to multiple addresses or a *.localhost address, we generate
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatting is busted here, but I can't tweak 😢 (forks!!!)

// additional URLs to indicate to the user other ways their service can be reached. If the service
// is bound to all interfaces (0.0.0.0, ::, etc.) we use the machine name as the additional
// address. If bound to a *.localhost address, we add the originally declared *.localhost address
// as an additional URL.
var additionalUrl = allocatedEndpoint.BindingMode switch
{
// Add the originally declared *.localhost URL
url = new ResourceUrlAnnotation { Url = $"{allocatedEndpoint.UriScheme}://{endpoint.TargetHost}:{allocatedEndpoint.Port}", Endpoint = endpointReference };
urls.Add(url);
// The allocated address doesn't match the original target host, so include the target host as
// an additional URL.
EndpointBindingMode.SingleAddress when !allocatedEndpoint.Address.Equals(endpoint.TargetHost, StringComparison.OrdinalIgnoreCase) => new ResourceUrlAnnotation
{
Url = $"{allocatedEndpoint.UriScheme}://{endpoint.TargetHost}:{allocatedEndpoint.Port}",
Endpoint = endpointReference,
},
// For other single address bindings ("localhost", specific IP), don't include an additional URL.
EndpointBindingMode.SingleAddress => null,
// All other cases are binding to some set of all interfaces (IPv4, IPv6, or both), so add the machine
// name as an additional URL.
_ => new ResourceUrlAnnotation
{
Url = $"{allocatedEndpoint.UriScheme}://{Environment.MachineName}:{allocatedEndpoint.Port}",
Endpoint = endpointReference,
},
};

if (additionalUrl is not null)
{
if (!existingUrls.Any(existingUrl => existingUrl.Url.Equals(additionalUrl.Url, StringComparison.OrdinalIgnoreCase) && existingUrl.Endpoint?.EndpointName == url.Endpoint?.EndpointName))
{
urls.Add(additionalUrl);
}
}
}
}
}

if (resource.TryGetUrls(out var existingUrls))
{
// Static URLs added to the resource via WithUrl(string name, string url), i.e. not callback-based
urls.AddRange(existingUrls);
}

// Run the URL callbacks
if (resource.TryGetAnnotationsOfType<ResourceUrlsCallbackAnnotation>(out var callbacks))
{
Expand All @@ -252,17 +280,6 @@ private async Task ProcessResourceUrlCallbacks(IResource resource, CancellationT
}
}

// Clear existing URLs
if (existingUrls is not null)
{
var existing = existingUrls.ToArray();
for (var i = existing.Length - 1; i >= 0; i--)
{
var url = existing[i];
resource.Annotations.Remove(url);
}
}

// Convert relative endpoint URLs to absolute URLs
foreach (var url in urls)
{
Expand Down
Loading