-
Notifications
You must be signed in to change notification settings - Fork 762
Allow HostUrl to remap both address and port #12521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
1e87bfd
da15e5a
c72f38a
d09b84a
6bc79de
e88e58f
037a613
904eecc
107669f
1a33f05
3d8b2de
83f2abf
0a5a17f
be99467
f62a759
8dbc77f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,10 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using Aspire.Hosting.Dcp; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.Extensions.Options; | ||
|
|
||
| namespace Aspire.Hosting.ApplicationModel; | ||
|
|
||
| /// <summary> | ||
|
|
@@ -13,26 +17,19 @@ public record HostUrl(string Url) : IValueProvider, IManifestExpressionProvider | |
| string IManifestExpressionProvider.ValueExpression => Url; | ||
|
|
||
| // Returns the url | ||
| ValueTask<string?> IValueProvider.GetValueAsync(System.Threading.CancellationToken _) => GetNetworkValueAsync(null); | ||
| ValueTask<string?> IValueProvider.GetValueAsync(System.Threading.CancellationToken cancellationToken) => ((IValueProvider)this).GetValueAsync(new(), cancellationToken); | ||
|
|
||
| // Returns the url | ||
| ValueTask<string?> IValueProvider.GetValueAsync(ValueProviderContext context, CancellationToken _) | ||
| async ValueTask<string?> IValueProvider.GetValueAsync(ValueProviderContext context, CancellationToken cancellationToken) | ||
| { | ||
| return context.Network switch | ||
| { | ||
| NetworkIdentifier networkContext => GetNetworkValueAsync(networkContext), | ||
| _ => GetNetworkValueAsync(null) | ||
| }; | ||
| } | ||
| var networkContext = context.GetNetworkIdentifier(); | ||
|
|
||
| private ValueTask<string?> GetNetworkValueAsync(NetworkIdentifier? context) | ||
| { | ||
| // HostUrl is a bit of a hack that is not modeled as an expression | ||
| // So in this one case, we need to fix up the container host name 'manually' | ||
| // Internally, this is only used for OTEL_EXPORTER_OTLP_ENDPOINT, but HostUrl | ||
| // is public, so we don't control how it is used | ||
|
|
||
| if (context is null || context == KnownNetworkIdentifiers.LocalhostNetwork) | ||
| if (networkContext == KnownNetworkIdentifiers.LocalhostNetwork) | ||
| { | ||
| return new(Url); | ||
| } | ||
|
|
@@ -44,25 +41,70 @@ public record HostUrl(string Url) : IValueProvider, IManifestExpressionProvider | |
| var uri = new UriBuilder(Url); | ||
| if (uri.Host is "localhost" or "127.0.0.1" or "[::1]") | ||
| { | ||
| var hasEndingSlash = Url.EndsWith('/'); | ||
| uri.Host = KnownHostNames.DefaultContainerTunnelHostName; | ||
| retval = uri.ToString(); | ||
|
|
||
| // Remove trailing slash if we didn't have one before (UriBuilder always adds one) | ||
| if (!hasEndingSlash && retval.EndsWith('/')) | ||
| if (context.ExecutionContext?.IsRunMode == true) | ||
danegsta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| retval = retval[..^1]; | ||
| var options = context.ExecutionContext.ServiceProvider.GetRequiredService<IOptions<DcpOptions>>(); | ||
|
|
||
| var infoService = context.ExecutionContext.ServiceProvider.GetRequiredService<IDcpDependencyCheckService>(); | ||
| var dcpInfo = await infoService.GetDcpInfoAsync(cancellationToken: cancellationToken).ConfigureAwait(false); | ||
|
|
||
| uri.Host = options.Value.EnableAspireContainerTunnel? KnownHostNames.DefaultContainerTunnelHostName : dcpInfo?.Containers?.ContainerHostName ?? KnownHostNames.DockerDesktopHostBridge; | ||
|
|
||
| if (options.Value.EnableAspireContainerTunnel) | ||
| { | ||
| // We need to consider that both the host and port may need to be remapped | ||
| var model = context.ExecutionContext.ServiceProvider.GetRequiredService<DistributedApplicationModel>(); | ||
| var targetEndpoint = model.Resources.Where(r => !r.IsContainer()) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to scan the entire model??
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All we know in HostUrl is an address and port, but both the address and port may need to change if running in a container. So I'm finding out if it's the URL to an existing endpoint and then mapping that to the appropriate container network endpoint if one exists. Ideally we'd just have a way to get the endpoints from the dashboard directly here and deprecate HostUrl entirely, but that's a larger change. |
||
| .OfType<IResourceWithEndpoints>() | ||
| .Select(r => | ||
| { | ||
| if (r.GetEndpoints(KnownNetworkIdentifiers.LocalhostNetwork).FirstOrDefault(ep => ep.Port == uri.Port) is EndpointReference ep) | ||
| { | ||
| return r.GetEndpoint(ep.EndpointName, networkContext); | ||
| } | ||
|
|
||
| return null; | ||
| }) | ||
| .Where(ep => ep is not null) | ||
| .FirstOrDefault(); | ||
|
|
||
| if (targetEndpoint is { }) | ||
| { | ||
| uri.Port = targetEndpoint.Port; | ||
| } | ||
| } | ||
|
|
||
| retval = uri.ToString(); | ||
| } | ||
| } | ||
|
|
||
| var hasEndingSlash = Url.EndsWith('/'); | ||
|
|
||
| // Remove trailing slash if we didn't have one before (UriBuilder always adds one) | ||
| if (!hasEndingSlash && retval.EndsWith('/')) | ||
| { | ||
| retval = retval[..^1]; | ||
| } | ||
| } | ||
| catch (UriFormatException) | ||
| { | ||
| var replacementHost = KnownHostNames.DockerDesktopHostBridge; | ||
| if (context.ExecutionContext?.IsRunMode == true) | ||
| { | ||
| var options = context.ExecutionContext.ServiceProvider.GetRequiredService<IOptions<DcpOptions>>(); | ||
|
|
||
| var infoService = context.ExecutionContext.ServiceProvider.GetRequiredService<IDcpDependencyCheckService>(); | ||
| var dcpInfo = await infoService.GetDcpInfoAsync(cancellationToken: cancellationToken).ConfigureAwait(false); | ||
|
|
||
| replacementHost = options.Value.EnableAspireContainerTunnel ? KnownHostNames.DefaultContainerTunnelHostName : dcpInfo?.Containers?.ContainerHostName ?? KnownHostNames.DockerDesktopHostBridge; | ||
| } | ||
|
|
||
| // HostUrl was meant to only be used with valid URLs. However, this was not | ||
| // previously enforced. So we need to handle the case where it's not a valid URL, | ||
| // by falling back to a simple string replacement. | ||
| retval = retval.Replace(KnownHostNames.Localhost, KnownHostNames.DefaultContainerTunnelHostName, StringComparison.OrdinalIgnoreCase) | ||
| .Replace("127.0.0.1", KnownHostNames.DefaultContainerTunnelHostName) | ||
| .Replace("[::1]", KnownHostNames.DefaultContainerTunnelHostName); | ||
| retval = retval.Replace(KnownHostNames.Localhost, replacementHost, StringComparison.OrdinalIgnoreCase) | ||
| .Replace("127.0.0.1", replacementHost) | ||
| .Replace("[::1]", replacementHost); | ||
| } | ||
|
|
||
| return new(retval); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.