diff --git a/playground/yarp/Yarp.AppHost/appsettings.json b/playground/yarp/Yarp.AppHost/appsettings.json new file mode 100644 index 00000000000..a1ada7711cd --- /dev/null +++ b/playground/yarp/Yarp.AppHost/appsettings.json @@ -0,0 +1,12 @@ +{ + "DcpPublisher": { + "EnableAspireContainerTunnel": true + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/src/Aspire.Hosting.Testing/DistributedApplicationHostingTestingExtensions.cs b/src/Aspire.Hosting.Testing/DistributedApplicationHostingTestingExtensions.cs index 8368bf5a7a2..2f043a7dcf0 100644 --- a/src/Aspire.Hosting.Testing/DistributedApplicationHostingTestingExtensions.cs +++ b/src/Aspire.Hosting.Testing/DistributedApplicationHostingTestingExtensions.cs @@ -68,7 +68,25 @@ public static Uri GetEndpoint(this DistributedApplication app, string resourceNa ArgumentNullException.ThrowIfNull(app); ArgumentException.ThrowIfNullOrEmpty(resourceName); - return new(GetEndpointUriStringCore(app, resourceName, endpointName)); + return GetEndpointForNetwork(app, resourceName, null, endpointName); + } + + /// + /// Gets the endpoint for the specified resource. + /// + /// The application. + /// The resource name. + /// The optional network identifier. If none is specified, the default network is used. + /// The optional endpoint name. If none are specified, the single defined endpoint is returned. + /// A URI representation of the endpoint. + /// The resource was not found, no matching endpoint was found, or multiple endpoints were found. + /// The resource has no endpoints. + public static Uri GetEndpointForNetwork(this DistributedApplication app, string resourceName, NetworkIdentifier? networkIdentifier, string? endpointName = default) + { + ArgumentNullException.ThrowIfNull(app); + ArgumentException.ThrowIfNullOrEmpty(resourceName); + + return new(GetEndpointUriStringCore(app, resourceName, endpointName, networkIdentifier)); } static IResource GetResource(DistributedApplication app, string resourceName) @@ -87,7 +105,7 @@ static IResource GetResource(DistributedApplication app, string resourceName) return resource; } - static string GetEndpointUriStringCore(DistributedApplication app, string resourceName, string? endpointName = default) + static string GetEndpointUriStringCore(DistributedApplication app, string resourceName, string? endpointName = default, NetworkIdentifier? networkIdentifier = default) { var resource = GetResource(app, resourceName); if (resource is not IResourceWithEndpoints resourceWithEndpoints) @@ -98,11 +116,11 @@ static string GetEndpointUriStringCore(DistributedApplication app, string resour EndpointReference? endpoint; if (!string.IsNullOrEmpty(endpointName)) { - endpoint = GetEndpointOrDefault(resourceWithEndpoints, endpointName); + endpoint = GetEndpointOrDefault(resourceWithEndpoints, endpointName, networkIdentifier); } else { - endpoint = GetEndpointOrDefault(resourceWithEndpoints, "http") ?? GetEndpointOrDefault(resourceWithEndpoints, "https"); + endpoint = GetEndpointOrDefault(resourceWithEndpoints, "http", networkIdentifier) ?? GetEndpointOrDefault(resourceWithEndpoints, "https", networkIdentifier); } if (endpoint is null) @@ -122,9 +140,9 @@ static void ThrowIfNotStarted(DistributedApplication app) } } - static EndpointReference? GetEndpointOrDefault(IResourceWithEndpoints resourceWithEndpoints, string endpointName) + static EndpointReference? GetEndpointOrDefault(IResourceWithEndpoints resourceWithEndpoints, string endpointName, NetworkIdentifier? networkIdentifier = default) { - var reference = resourceWithEndpoints.GetEndpoint(endpointName); + var reference = resourceWithEndpoints.GetEndpoint(endpointName, networkIdentifier ?? KnownNetworkIdentifiers.LocalhostNetwork); return reference.IsAllocated ? reference : null; } diff --git a/src/Aspire.Hosting/ApplicationModel/ConnectionStringReference.cs b/src/Aspire.Hosting/ApplicationModel/ConnectionStringReference.cs index a8804c193fa..9d464b9cd6e 100644 --- a/src/Aspire.Hosting/ApplicationModel/ConnectionStringReference.cs +++ b/src/Aspire.Hosting/ApplicationModel/ConnectionStringReference.cs @@ -23,22 +23,12 @@ public class ConnectionStringReference(IResourceWithConnectionString resource, b ValueTask IValueProvider.GetValueAsync(CancellationToken cancellationToken) { - return this.GetNetworkValueAsync(null, cancellationToken); + return Resource.GetValueAsync(cancellationToken); } - ValueTask IValueProvider.GetValueAsync(ValueProviderContext context, CancellationToken cancellationToken) + async ValueTask IValueProvider.GetValueAsync(ValueProviderContext context, CancellationToken cancellationToken) { - return context.Network switch - { - NetworkIdentifier networkContext => GetNetworkValueAsync(networkContext, cancellationToken), - _ => GetNetworkValueAsync(null, cancellationToken) - }; - } - - private async ValueTask GetNetworkValueAsync(NetworkIdentifier? networkContext, CancellationToken cancellationToken) - { - ValueProviderContext vpc = new() { Network = networkContext }; - var value = await Resource.GetValueAsync(vpc, cancellationToken).ConfigureAwait(false); + var value = await Resource.GetValueAsync(context, cancellationToken).ConfigureAwait(false); if (string.IsNullOrEmpty(value) && !Optional) { diff --git a/src/Aspire.Hosting/ApplicationModel/EndpointReference.cs b/src/Aspire.Hosting/ApplicationModel/EndpointReference.cs index 85b21887452..92d03361129 100644 --- a/src/Aspire.Hosting/ApplicationModel/EndpointReference.cs +++ b/src/Aspire.Hosting/ApplicationModel/EndpointReference.cs @@ -15,7 +15,7 @@ public sealed class EndpointReference : IManifestExpressionProvider, IValueProvi // A reference to the endpoint annotation if it exists. private EndpointAnnotation? _endpointAnnotation; private bool? _isAllocated; - private readonly NetworkIdentifier _contextNetworkID; + private readonly NetworkIdentifier? _contextNetworkID; /// /// Gets the endpoint annotation associated with the endpoint reference. @@ -71,7 +71,7 @@ public sealed class EndpointReference : IManifestExpressionProvider, IValueProvi /// The reference will be resolved in the context of this network, which may be different /// from the network associated with the default network of the referenced Endpoint. /// - public NetworkIdentifier ContextNetworkID => _contextNetworkID; + public NetworkIdentifier? ContextNetworkID => _contextNetworkID; /// /// Gets the specified property expression of the endpoint. Defaults to the URL if no property is specified. @@ -156,7 +156,7 @@ public EndpointReferenceExpression Property(EndpointProperty property) foreach (var nes in endpointAnnotation.AllAllocatedEndpoints) { - if (StringComparers.NetworkID.Equals(nes.NetworkID, _contextNetworkID)) + if (StringComparers.NetworkID.Equals(nes.NetworkID, _contextNetworkID ?? KnownNetworkIdentifiers.LocalhostNetwork)) { if (!nes.Snapshot.IsValueSet) { @@ -177,12 +177,12 @@ public EndpointReferenceExpression Property(EndpointProperty property) /// The endpoint annotation. /// The ID of the network that serves as the context for the EndpointReference. /// - /// Most Aspire resources are accessed in the context of the "localhost" network (host proceses calling other host processes, - /// or host processes calling container via mapped ports). This is why EndpointReference assumes this - /// context unless specified otherwise. However, for container-to-container, or container-to-host communication, - /// you must specify a container network context for the EndpointReference to be resolved correctly. + /// Most Aspire resources are accessed in the context of the "localhost" network (host processes calling other host processes, + /// or host processes calling container via mapped ports). If a is specified, the + /// will always resolve in the context of that network. If the is null, the reference will attempt to resolve itself + /// based on the context of the requesting resource. /// - public EndpointReference(IResourceWithEndpoints owner, EndpointAnnotation endpoint, NetworkIdentifier? contextNetworkID = null) + public EndpointReference(IResourceWithEndpoints owner, EndpointAnnotation endpoint, NetworkIdentifier? contextNetworkID) { ArgumentNullException.ThrowIfNull(owner); ArgumentNullException.ThrowIfNull(endpoint); @@ -190,7 +190,7 @@ public EndpointReference(IResourceWithEndpoints owner, EndpointAnnotation endpoi Resource = owner; EndpointName = endpoint.Name; _endpointAnnotation = endpoint; - _contextNetworkID = contextNetworkID ?? KnownNetworkIdentifiers.LocalhostNetwork; + _contextNetworkID = contextNetworkID; } /// @@ -221,7 +221,7 @@ public EndpointReference(IResourceWithEndpoints owner, string endpointName, Netw Resource = owner; EndpointName = endpointName; - _contextNetworkID = contextNetworkID ?? KnownNetworkIdentifiers.LocalhostNetwork; + _contextNetworkID = contextNetworkID; } /// @@ -265,7 +265,7 @@ public class EndpointReferenceExpression(EndpointReference endpointReference, En /// Throws when the selected enumeration is not known. public ValueTask GetValueAsync(CancellationToken cancellationToken = default) { - return GetNetworkValueAsync(null, cancellationToken); + return GetValueAsync(new(), cancellationToken); } /// @@ -275,33 +275,25 @@ public class EndpointReferenceExpression(EndpointReference endpointReference, En /// A . /// A containing the selected value. /// Throws when the selected enumeration is not known. - public ValueTask GetValueAsync(ValueProviderContext context, CancellationToken cancellationToken = default) + public async ValueTask GetValueAsync(ValueProviderContext context, CancellationToken cancellationToken = default) { - return context.Network switch - { - NetworkIdentifier networkID => GetNetworkValueAsync(networkID, cancellationToken), - _ => GetNetworkValueAsync(null, cancellationToken) - }; - } + // If the EndpointReference was for a specific network context, then use that. Otherwise, use the network context from the ValueProviderContext. + // This allows the EndpointReference to be resolved in the context of the caller's network if it was not explicitly set. + var networkContext = Endpoint.ContextNetworkID ?? context.GetNetworkIdentifier(); - - private async ValueTask GetNetworkValueAsync(NetworkIdentifier? context, CancellationToken cancellationToken = default) - { return Property switch { EndpointProperty.Scheme => new(Endpoint.Scheme), - EndpointProperty.IPV4Host when context is null || context == KnownNetworkIdentifiers.LocalhostNetwork => "127.0.0.1", + EndpointProperty.IPV4Host when networkContext == KnownNetworkIdentifiers.LocalhostNetwork => "127.0.0.1", EndpointProperty.TargetPort when Endpoint.TargetPort is int port => new(port.ToString(CultureInfo.InvariantCulture)), _ => await ResolveValueWithAllocatedAddress().ConfigureAwait(false) }; async ValueTask ResolveValueWithAllocatedAddress() { - var effectiveContext = context ?? Endpoint.ContextNetworkID; - // We are going to take the first snapshot that matches the context network ID. In general there might be multiple endpoints for a single service, // and in future we might need some sort of policy to choose between them, but for now we just take the first one. - var nes = Endpoint.EndpointAnnotation.AllAllocatedEndpoints.Where(nes => nes.NetworkID == effectiveContext).FirstOrDefault(); + var nes = Endpoint.EndpointAnnotation.AllAllocatedEndpoints.Where(nes => nes.NetworkID == networkContext).FirstOrDefault(); if (nes is null) { return null; diff --git a/src/Aspire.Hosting/ApplicationModel/ExpressionResolver.cs b/src/Aspire.Hosting/ApplicationModel/ExpressionResolver.cs index 5127f1558e6..1fdea798226 100644 --- a/src/Aspire.Hosting/ApplicationModel/ExpressionResolver.cs +++ b/src/Aspire.Hosting/ApplicationModel/ExpressionResolver.cs @@ -95,13 +95,14 @@ async Task ResolveConnectionStringReferenceAsync(ConnectionString /// async ValueTask ResolveInternalAsync(object? value, ValueProviderContext context) { + var networkContext = context.GetNetworkIdentifier(); return value switch { ConnectionStringReference cs => await ResolveConnectionStringReferenceAsync(cs, context).ConfigureAwait(false), IResourceWithConnectionString cs and not ConnectionStringParameterResource => await ResolveInternalAsync(cs.ConnectionStringExpression, context).ConfigureAwait(false), ReferenceExpression ex => await EvalExpressionAsync(ex, context).ConfigureAwait(false), - EndpointReference er when context.Network == KnownNetworkIdentifiers.DefaultAspireContainerNetwork => new ResolvedValue(await ResolveInContainerContextAsync(er, EndpointProperty.Url, context).ConfigureAwait(false), false), - EndpointReferenceExpression ep when context.Network == KnownNetworkIdentifiers.DefaultAspireContainerNetwork => new ResolvedValue(await ResolveInContainerContextAsync(ep.Endpoint, ep.Property, context).ConfigureAwait(false), false), + EndpointReference er when networkContext == KnownNetworkIdentifiers.DefaultAspireContainerNetwork => new ResolvedValue(await ResolveInContainerContextAsync(er, EndpointProperty.Url, context).ConfigureAwait(false), false), + EndpointReferenceExpression ep when networkContext == KnownNetworkIdentifiers.DefaultAspireContainerNetwork => new ResolvedValue(await ResolveInContainerContextAsync(ep.Endpoint, ep.Property, context).ConfigureAwait(false), false), IValueProvider vp => await EvalValueProvider(vp, context).ConfigureAwait(false), _ => throw new NotImplementedException() }; diff --git a/src/Aspire.Hosting/ApplicationModel/HostUrl.cs b/src/Aspire.Hosting/ApplicationModel/HostUrl.cs index 51a732af4ef..ef29422c12c 100644 --- a/src/Aspire.Hosting/ApplicationModel/HostUrl.cs +++ b/src/Aspire.Hosting/ApplicationModel/HostUrl.cs @@ -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; /// @@ -13,26 +17,19 @@ public record HostUrl(string Url) : IValueProvider, IManifestExpressionProvider string IManifestExpressionProvider.ValueExpression => Url; // Returns the url - ValueTask IValueProvider.GetValueAsync(System.Threading.CancellationToken _) => GetNetworkValueAsync(null); + ValueTask IValueProvider.GetValueAsync(System.Threading.CancellationToken cancellationToken) => ((IValueProvider)this).GetValueAsync(new(), cancellationToken); // Returns the url - ValueTask IValueProvider.GetValueAsync(ValueProviderContext context, CancellationToken _) + async ValueTask IValueProvider.GetValueAsync(ValueProviderContext context, CancellationToken cancellationToken) { - return context.Network switch - { - NetworkIdentifier networkContext => GetNetworkValueAsync(networkContext), - _ => GetNetworkValueAsync(null) - }; - } + var networkContext = context.GetNetworkIdentifier(); - private ValueTask 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,81 @@ 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) { - retval = retval[..^1]; + // HostUrl isn't modeled as an expression, so we have to find the appropriate allocated endpoint to use manually in the case we're running in a container. + // We're given a URL from the point of view of the host, so need to figure how to modify the URL to be correct from the point of view of the container. + // This could simply be replacing the hostname, but if the container tunnel is running, we may need to translate the port as well. + // Without doing this, we wouldn't be able to resolve the OTEL address correctly from a container as it currently depends on HostUrl rather than the dashboard endpoints. + var options = context.ExecutionContext.ServiceProvider.GetRequiredService>(); + + var infoService = context.ExecutionContext.ServiceProvider.GetRequiredService(); + var dcpInfo = await infoService.GetDcpInfoAsync(cancellationToken: cancellationToken).ConfigureAwait(false); + + // Determine what hostname means that we want to contact the host machine from the container. If using the new tunnel feature, this needs to be the address of the tunnel instance. + // Otherwise we want to try and determine the container runtime appropriate hostname (host.docker.internal or host.containers.internal). + uri.Host = options.Value.EnableAspireContainerTunnel? KnownHostNames.DefaultContainerTunnelHostName : dcpInfo?.Containers?.ContainerHostName ?? KnownHostNames.DockerDesktopHostBridge; + + if (options.Value.EnableAspireContainerTunnel) + { + // If we're running with the container tunnel enabled, we need to lookup the port on the tunnel that corresponds to the + // target port on the host machine. + var model = context.ExecutionContext.ServiceProvider.GetRequiredService(); + var targetEndpoint = model.Resources.Where(r => !r.IsContainer()) + .OfType() + .Select(r => + { + // Find if the resource has a host endpoint with a port matching the one from the request + if (r.GetEndpoints(KnownNetworkIdentifiers.LocalhostNetwork).FirstOrDefault(ep => ep.Port == uri.Port) is EndpointReference ep) + { + // Return the corresponding endpoint for the container network context. This will be used to determine the port to use when connecting from the container to the host machine. + return r.GetEndpoint(ep.EndpointName, networkContext); + } + + return null; + }) + .Where(ep => ep is not null) + .FirstOrDefault(); + + if (targetEndpoint is { }) + { + // If we found a container endpoint, remap the requested port + 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) { + // This was a connection string style value instead of a URL. In that case we'll do a simple hostname replacement, but can't do anything about ports. + var replacementHost = KnownHostNames.DockerDesktopHostBridge; + if (context.ExecutionContext?.IsRunMode == true) + { + var options = context.ExecutionContext.ServiceProvider.GetRequiredService>(); + + var infoService = context.ExecutionContext.ServiceProvider.GetRequiredService(); + 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); diff --git a/src/Aspire.Hosting/ApplicationModel/IResourceWithConnectionString.cs b/src/Aspire.Hosting/ApplicationModel/IResourceWithConnectionString.cs index 4750d40b7a6..c60dfb1f1a3 100644 --- a/src/Aspire.Hosting/ApplicationModel/IResourceWithConnectionString.cs +++ b/src/Aspire.Hosting/ApplicationModel/IResourceWithConnectionString.cs @@ -20,11 +20,8 @@ public interface IResourceWithConnectionString : IResource, IManifestExpressionP ValueTask IValueProvider.GetValueAsync(CancellationToken cancellationToken) => GetConnectionStringAsync(cancellationToken); - ValueTask IValueProvider.GetValueAsync(ValueProviderContext context, CancellationToken cancellationToken) => context.Network switch - { - NetworkIdentifier networkContext => ConnectionStringExpression.GetValueAsync(new ValueProviderContext { Network = networkContext }, cancellationToken), - _ => GetConnectionStringAsync(cancellationToken), - }; + ValueTask IValueProvider.GetValueAsync(ValueProviderContext context, CancellationToken cancellationToken) => + ConnectionStringExpression.GetValueAsync(context, cancellationToken); /// /// Describes the connection string format string used for this resource. diff --git a/src/Aspire.Hosting/ApplicationModel/IValueProvider.cs b/src/Aspire.Hosting/ApplicationModel/IValueProvider.cs index 2c5e42f517d..afc80841c28 100644 --- a/src/Aspire.Hosting/ApplicationModel/IValueProvider.cs +++ b/src/Aspire.Hosting/ApplicationModel/IValueProvider.cs @@ -9,9 +9,9 @@ namespace Aspire.Hosting.ApplicationModel; public class ValueProviderContext { /// - /// Additional services that can be used during value resolution. + /// The execution context for the distributed application. /// - public IServiceProvider? Services { get; init; } + public DistributedApplicationExecutionContext? ExecutionContext { get; init; } /// /// The resource that is requesting the value. diff --git a/src/Aspire.Hosting/ApplicationModel/ReferenceExpression.cs b/src/Aspire.Hosting/ApplicationModel/ReferenceExpression.cs index e96764eff72..169ce1382fd 100644 --- a/src/Aspire.Hosting/ApplicationModel/ReferenceExpression.cs +++ b/src/Aspire.Hosting/ApplicationModel/ReferenceExpression.cs @@ -98,7 +98,7 @@ private ReferenceExpression(string format, IValueProvider[] valueProviders, stri /// A . public ValueTask GetValueAsync(CancellationToken cancellationToken) { - return this.GetValueAsync(new ValueProviderContext(), cancellationToken); + return this.GetValueAsync(new(), cancellationToken); } internal static ReferenceExpression Create(string format, IValueProvider[] valueProviders, string[] manifestExpressions, string?[] stringFormats) diff --git a/src/Aspire.Hosting/ApplicationModel/ResourceExtensions.cs b/src/Aspire.Hosting/ApplicationModel/ResourceExtensions.cs index 28aeacf7395..42229af1c16 100644 --- a/src/Aspire.Hosting/ApplicationModel/ResourceExtensions.cs +++ b/src/Aspire.Hosting/ApplicationModel/ResourceExtensions.cs @@ -271,7 +271,6 @@ await resource.ProcessArgumentValuesAsync( /// /// The logger used for logging information or errors during the argument processing. /// A token for cancelling the operation, if needed. - /// An optional network identifier providing context for resolving network-related values. /// A task representing the asynchronous operation. public static async ValueTask ProcessArgumentValuesAsync( this IResource resource, @@ -279,8 +278,7 @@ public static async ValueTask ProcessArgumentValuesAsync( // (unprocessed, processed, exception, isSensitive) Action processValue, ILogger logger, - CancellationToken cancellationToken = default, - NetworkIdentifier? networkContext = null) + CancellationToken cancellationToken = default) { if (resource.TryGetAnnotationsOfType(out var callbacks)) { @@ -296,13 +294,11 @@ public static async ValueTask ProcessArgumentValuesAsync( await callback.Callback(context).ConfigureAwait(false); } - networkContext ??= resource.GetDefaultResourceNetwork(); - foreach (var a in args) { try { - var resolvedValue = await ResolveValueAsync(executionContext, logger, a, null, networkContext, cancellationToken).ConfigureAwait(false); + var resolvedValue = await resource.ResolveValueAsync(executionContext, logger, a, null, cancellationToken).ConfigureAwait(false); if (resolvedValue?.Value != null) { @@ -325,15 +321,13 @@ public static async ValueTask ProcessArgumentValuesAsync( /// An action delegate invoked for each environment variable, providing the key, the unprocessed value, the processed value (if available), and any exception encountered during processing. /// The logger used to log any information or errors during the environment variables processing. /// A cancellation token to observe during the asynchronous operation. - /// An optional network identifier providing context for resolving network-related values. /// A task that represents the asynchronous operation. public static async ValueTask ProcessEnvironmentVariableValuesAsync( this IResource resource, DistributedApplicationExecutionContext executionContext, Action processValue, ILogger logger, - CancellationToken cancellationToken = default, - NetworkIdentifier? networkContext = null) + CancellationToken cancellationToken = default) { if (resource.TryGetEnvironmentVariables(out var callbacks)) { @@ -348,13 +342,11 @@ public static async ValueTask ProcessEnvironmentVariableValuesAsync( await callback.Callback(context).ConfigureAwait(false); } - networkContext ??= resource.GetDefaultResourceNetwork(); - foreach (var (key, expr) in config) { try { - var resolvedValue = await ResolveValueAsync(executionContext, logger, expr, key, networkContext, cancellationToken).ConfigureAwait(false); + var resolvedValue = await resource.ResolveValueAsync(executionContext, logger, expr, key, cancellationToken).ConfigureAwait(false); if (resolvedValue?.Value is not null) { @@ -374,12 +366,17 @@ internal static NetworkIdentifier GetDefaultResourceNetwork(this IResource resou return resource.IsContainer() ? KnownNetworkIdentifiers.DefaultAspireContainerNetwork : KnownNetworkIdentifiers.LocalhostNetwork; } + internal static IEnumerable GetSupportedNetworks(this IResource resource) + { + return resource.IsContainer() ? [KnownNetworkIdentifiers.DefaultAspireContainerNetwork, KnownNetworkIdentifiers.LocalhostNetwork] : [KnownNetworkIdentifiers.LocalhostNetwork]; + } + /// /// Processes trusted certificates configuration for the specified resource within the given execution context. /// This may produce additional and /// annotations on the resource to configure certificate trust as needed and therefore must be run before - /// - /// and are called. + /// + /// and are called. /// /// The resource for which to process the certificate trust configuration. /// The execution context used during the processing. @@ -388,7 +385,6 @@ internal static NetworkIdentifier GetDefaultResourceNetwork(this IResource resou /// The logger used for logging information during the processing. /// A function that takes the active and returns a representing the path to a custom certificate bundle for the resource. /// A function that takes the active and returns a representing path(s) to a directory containing the custom certificates for the resource. - /// An optional network identifier providing context for resolving network-related values. /// A cancellation token to observe while processing. /// A task that represents the asynchronous operation. internal static async ValueTask<(CertificateTrustScope, X509Certificate2Collection?)> ProcessCertificateTrustConfigAsync( @@ -401,7 +397,6 @@ internal static NetworkIdentifier GetDefaultResourceNetwork(this IResource resou ILogger logger, Func bundlePathFactory, Func certificateDirectoryPathsFactory, - NetworkIdentifier? networkContext = null, CancellationToken cancellationToken = default) { #pragma warning disable ASPIRECERTIFICATES001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. @@ -496,7 +491,7 @@ internal static NetworkIdentifier GetDefaultResourceNetwork(this IResource resou { try { - var resolvedValue = await ResolveValueAsync(executionContext, logger, a, null, networkContext, cancellationToken).ConfigureAwait(false); + var resolvedValue = await resource.ResolveValueAsync(executionContext, logger, a, null, cancellationToken).ConfigureAwait(false); if (resolvedValue?.Value != null) { @@ -513,7 +508,7 @@ internal static NetworkIdentifier GetDefaultResourceNetwork(this IResource resou { try { - var resolvedValue = await ResolveValueAsync(executionContext, logger, expr, key, networkContext, cancellationToken).ConfigureAwait(false); + var resolvedValue = await resource.ResolveValueAsync(executionContext, logger, expr, key, cancellationToken).ConfigureAwait(false); if (resolvedValue?.Value is not null) { @@ -530,18 +525,18 @@ internal static NetworkIdentifier GetDefaultResourceNetwork(this IResource resou } private static async ValueTask ResolveValueAsync( + this IResource resource, DistributedApplicationExecutionContext executionContext, ILogger logger, - object value, + object? value, string? key = null, - NetworkIdentifier? networkContext = null, CancellationToken cancellationToken = default) { return (executionContext.Operation, value) switch { (_, string s) => new(s, false), - (DistributedApplicationOperation.Run, IValueProvider provider) => await GetValue(key, provider, logger, networkContext, cancellationToken).ConfigureAwait(false), - (DistributedApplicationOperation.Run, IResourceBuilder rb) when rb.Resource is IValueProvider provider => await GetValue(key, provider, logger, networkContext, cancellationToken).ConfigureAwait(false), + (DistributedApplicationOperation.Run, IValueProvider provider) => await resource.GetValue(executionContext, key, provider, logger, cancellationToken).ConfigureAwait(false), + (DistributedApplicationOperation.Run, IResourceBuilder rb) when rb.Resource is IValueProvider provider => await resource.GetValue(executionContext, key, provider, logger, cancellationToken).ConfigureAwait(false), (DistributedApplicationOperation.Publish, IManifestExpressionProvider provider) => new(provider.ValueExpression, false), (DistributedApplicationOperation.Publish, IResourceBuilder rb) when rb.Resource is IManifestExpressionProvider provider => new(provider.ValueExpression, false), (_, { } o) => new(o.ToString(), false), @@ -558,10 +553,10 @@ public static bool IsExcludedFromPublish(this IResource resource) => internal static async ValueTask ProcessContainerRuntimeArgValues( this IResource resource, + DistributedApplicationExecutionContext executionContext, Action processValue, ILogger logger, - CancellationToken cancellationToken = default, - NetworkIdentifier? context = null) + CancellationToken cancellationToken = default) { // Apply optional extra arguments to the container run command. if (resource.TryGetAnnotationsOfType(out var runArgsCallback)) @@ -582,7 +577,7 @@ internal static async ValueTask ProcessContainerRuntimeArgValues( var value = arg switch { string s => s, - IValueProvider valueProvider => (await GetValue(key: null, valueProvider, logger, context, cancellationToken).ConfigureAwait(false))?.Value, + IValueProvider valueProvider => (await resource.GetValue(executionContext, key: null, valueProvider, logger, cancellationToken).ConfigureAwait(false))?.Value, { } obj => obj.ToString(), null => null }; @@ -600,21 +595,21 @@ internal static async ValueTask ProcessContainerRuntimeArgValues( } } - private static async Task GetValue(string? key, IValueProvider valueProvider, ILogger logger, NetworkIdentifier? networkContext, CancellationToken cancellationToken) + private static async Task GetValue(this IResource resource, DistributedApplicationExecutionContext executionContext, string? key, IValueProvider valueProvider, ILogger logger, CancellationToken cancellationToken) { - var task = ExpressionResolver.ResolveAsync(valueProvider, new ValueProviderContext() { Network = networkContext }, cancellationToken); + var task = ExpressionResolver.ResolveAsync(valueProvider, new ValueProviderContext() { ExecutionContext = executionContext, Caller = resource }, cancellationToken); if (!task.IsCompleted) { - if (valueProvider is IResource resource) + if (valueProvider is IResource providerResource) { if (key is null) { - logger.LogInformation("Waiting for value from resource '{ResourceName}'", resource.Name); + logger.LogInformation("Waiting for value from resource '{ResourceName}'", providerResource.Name); } else { - logger.LogInformation("Waiting for value for environment variable value '{Name}' from resource '{ResourceName}'", key, resource.Name); + logger.LogInformation("Waiting for value for environment variable value '{Name}' from resource '{ResourceName}'", key, providerResource.Name); } } else if (valueProvider is ConnectionStringReference { Resource: var cs }) diff --git a/src/Aspire.Hosting/ApplicationModel/ValueProviderExtensions.cs b/src/Aspire.Hosting/ApplicationModel/ValueProviderExtensions.cs new file mode 100644 index 00000000000..49914efd985 --- /dev/null +++ b/src/Aspire.Hosting/ApplicationModel/ValueProviderExtensions.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +internal static class ValueProviderExtensions +{ + public static NetworkIdentifier GetNetworkIdentifier(this ValueProviderContext context) + { + return context?.Network ?? context?.Caller?.GetDefaultResourceNetwork() ?? KnownNetworkIdentifiers.LocalhostNetwork; + } +} diff --git a/src/Aspire.Hosting/Aspire.Hosting.csproj b/src/Aspire.Hosting/Aspire.Hosting.csproj index 969b4d80999..079083aadfe 100644 --- a/src/Aspire.Hosting/Aspire.Hosting.csproj +++ b/src/Aspire.Hosting/Aspire.Hosting.csproj @@ -105,6 +105,7 @@ + diff --git a/src/Aspire.Hosting/CompatibilitySuppressions.xml b/src/Aspire.Hosting/CompatibilitySuppressions.xml index 09661f93d68..fb019b17b48 100644 --- a/src/Aspire.Hosting/CompatibilitySuppressions.xml +++ b/src/Aspire.Hosting/CompatibilitySuppressions.xml @@ -1,4 +1,4 @@ - + diff --git a/src/Aspire.Hosting/Dashboard/DashboardEventHandlers.cs b/src/Aspire.Hosting/Dashboard/DashboardEventHandlers.cs index f4eb4c6d30a..77ae160fcbe 100644 --- a/src/Aspire.Hosting/Dashboard/DashboardEventHandlers.cs +++ b/src/Aspire.Hosting/Dashboard/DashboardEventHandlers.cs @@ -612,26 +612,26 @@ private static void PopulateDashboardUrls(EnvironmentCallbackContext context) static ReferenceExpression GetTargetUrlExpression(EndpointReference e) => ReferenceExpression.Create($"{e.Property(EndpointProperty.Scheme)}://{e.EndpointAnnotation.TargetHost}:{e.Property(EndpointProperty.TargetPort)}"); - var otlpGrpc = dashboardResource.GetEndpoint(OtlpGrpcEndpointName); + var otlpGrpc = dashboardResource.GetEndpoint(OtlpGrpcEndpointName, KnownNetworkIdentifiers.LocalhostNetwork); if (otlpGrpc.Exists) { context.EnvironmentVariables[DashboardConfigNames.DashboardOtlpGrpcUrlName.EnvVarName] = GetTargetUrlExpression(otlpGrpc); } - var otlpHttp = dashboardResource.GetEndpoint(OtlpHttpEndpointName); + var otlpHttp = dashboardResource.GetEndpoint(OtlpHttpEndpointName, KnownNetworkIdentifiers.LocalhostNetwork); if (otlpHttp.Exists) { context.EnvironmentVariables[DashboardConfigNames.DashboardOtlpHttpUrlName.EnvVarName] = GetTargetUrlExpression(otlpHttp); } - var mcp = dashboardResource.GetEndpoint(McpEndpointName); + var mcp = dashboardResource.GetEndpoint(McpEndpointName, KnownNetworkIdentifiers.LocalhostNetwork); if (!mcp.Exists) { // Fallback to frontend https or http endpoint if not configured. - mcp = dashboardResource.GetEndpoint("https"); + mcp = dashboardResource.GetEndpoint("https", KnownNetworkIdentifiers.LocalhostNetwork); if (!mcp.Exists) { - mcp = dashboardResource.GetEndpoint("http"); + mcp = dashboardResource.GetEndpoint("http", KnownNetworkIdentifiers.LocalhostNetwork); } } @@ -644,7 +644,7 @@ static ReferenceExpression GetTargetUrlExpression(EndpointReference e) => context.EnvironmentVariables[DashboardConfigNames.DashboardMcpUrlName.EnvVarName] = GetTargetUrlExpression(mcp); } - var frontendEndpoints = dashboardResource.GetEndpoints().ToList(); + var frontendEndpoints = dashboardResource.GetEndpoints(KnownNetworkIdentifiers.LocalhostNetwork).ToList(); var aspnetCoreUrls = new ReferenceExpressionBuilder(); var first = true; diff --git a/src/Aspire.Hosting/Dcp/DcpExecutor.cs b/src/Aspire.Hosting/Dcp/DcpExecutor.cs index 91985887b46..c9c21de8842 100644 --- a/src/Aspire.Hosting/Dcp/DcpExecutor.cs +++ b/src/Aspire.Hosting/Dcp/DcpExecutor.cs @@ -135,7 +135,7 @@ public DcpExecutor(ILogger logger, } private string ContainerHostName => _configuration["AppHost:ContainerHostname"] ?? - (_options.Value.EnableAspireContainerTunnel ? KnownHostNames.DefaultContainerTunnelHostName : KnownHostNames.DockerDesktopHostBridge); + (_options.Value.EnableAspireContainerTunnel ? KnownHostNames.DefaultContainerTunnelHostName : _dcpInfo?.Containers?.HostName ?? KnownHostNames.DockerDesktopHostBridge); public async Task RunApplicationAsync(CancellationToken cancellationToken = default) { @@ -850,7 +850,7 @@ private async Task EnsureContainerServiceAddressInfo(CancellationToken cancellat } else { - // Container services are services that "mirror" their primary (host) service counterparts, but expose addresses usable from container network. + // Container services are services that "mirror" their primary (host) service counterparts, but expose addresses usable from container network. // We just need to update their ports from primary services, changing the address to container host. var containerServices = _appResources.Where(r => r.DcpResource is Service { }).Select(r => ( Service: r.DcpResource as Service, @@ -1190,7 +1190,6 @@ private void PrepareServices() svc.Annotate(CustomResource.ContainerTunnelInstanceName, tunnelProxy?.Metadata?.Name ?? ""); var svcAppResource = new ServiceAppResource(svc); - _appResources.Add(svcAppResource); if (useTunnel) @@ -2270,6 +2269,7 @@ await modelResource.ProcessEnvironmentVariableValuesAsync( var runArgs = new List(); await modelResource.ProcessContainerRuntimeArgValues( + _executionContext, (a, ex) => { if (ex is not null) @@ -2342,7 +2342,6 @@ await modelResource.ProcessContainerRuntimeArgValues( resourceLogger, (scope) => ReferenceExpression.Create($"{bundleOutputPath}"), (scope) => ReferenceExpression.Create($"{certificatesOutputPath}"), - networkContext: null, cancellationToken).ConfigureAwait(false); if (certificates?.Any() == true) @@ -2394,7 +2393,6 @@ await modelResource.ProcessContainerRuntimeArgValues( var env = new List(); var createFiles = new List(); - var pathsProvider = new CertificateTrustConfigurationPathsProvider(); (var scope, var certificates) = await modelResource.ProcessCertificateTrustConfigAsync( _executionContext, (unprocessed, value, ex, isSensitive) => @@ -2439,7 +2437,6 @@ await modelResource.ProcessContainerRuntimeArgValues( // Build Linux PATH style colon-separated list of directories return ReferenceExpression.Create($"{string.Join(':', dirs)}"); }, - networkContext: null, cancellationToken).ConfigureAwait(false); if (certificates?.Any() == true) diff --git a/src/Aspire.Hosting/OtlpConfigurationExtensions.cs b/src/Aspire.Hosting/OtlpConfigurationExtensions.cs index 644361bf7fb..f5d79d72da6 100644 --- a/src/Aspire.Hosting/OtlpConfigurationExtensions.cs +++ b/src/Aspire.Hosting/OtlpConfigurationExtensions.cs @@ -168,7 +168,7 @@ public static IResourceBuilder WithOtlpExporter(this IResourceBuilder b ArgumentNullException.ThrowIfNull(builder); AddOtlpEnvironment(builder.Resource, builder.ApplicationBuilder.Configuration, builder.ApplicationBuilder.Environment); - + return builder; } diff --git a/tests/Aspire.Hosting.Tests/Dashboard/DashboardResourceTests.cs b/tests/Aspire.Hosting.Tests/Dashboard/DashboardResourceTests.cs index 42cfeade6b1..91fb2f62d1b 100644 --- a/tests/Aspire.Hosting.Tests/Dashboard/DashboardResourceTests.cs +++ b/tests/Aspire.Hosting.Tests/Dashboard/DashboardResourceTests.cs @@ -112,7 +112,7 @@ public async Task DashboardDoesNotAddResource_ConfiguresExistingDashboard(string Assert.Same(container.Resource, dashboard); - var config = (await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(dashboard, DistributedApplicationOperation.Run, TestServiceProvider.Instance, KnownNetworkIdentifiers.LocalhostNetwork).DefaultTimeout()) + var config = (await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(dashboard, DistributedApplicationOperation.Run, TestServiceProvider.Instance).DefaultTimeout()) .OrderBy(c => c.Key) .ToList(); @@ -214,7 +214,7 @@ public async Task DashboardDoesNotAddResource_ConfiguresMcpEndpoint(int expected Assert.Same(container.Resource, dashboard); - var config = (await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(dashboard, DistributedApplicationOperation.Run, TestServiceProvider.Instance, KnownNetworkIdentifiers.LocalhostNetwork).DefaultTimeout()) + var config = (await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(dashboard, DistributedApplicationOperation.Run, TestServiceProvider.Instance).DefaultTimeout()) .OrderBy(c => c.Key) .ToList(); diff --git a/tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs b/tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs index e2787f0fb67..c78567bc71c 100644 --- a/tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs +++ b/tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs @@ -2036,6 +2036,7 @@ private static DcpExecutor CreateAppExecutor( { ServiceProvider = new TestServiceProvider(configuration) .AddService(developerCertificateService) + .AddService(Options.Create(dcpOptions)) }), resourceLoggerService, new TestDcpDependencyCheckService(), diff --git a/tests/Aspire.Hosting.Tests/ExpressionResolverTests.cs b/tests/Aspire.Hosting.Tests/ExpressionResolverTests.cs index b9b06627a9d..65d574e1610 100644 --- a/tests/Aspire.Hosting.Tests/ExpressionResolverTests.cs +++ b/tests/Aspire.Hosting.Tests/ExpressionResolverTests.cs @@ -1,8 +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 Aspire.Hosting.Tests.Utils; using Microsoft.AspNetCore.InternalTesting; +using Microsoft.Extensions.Options; namespace Aspire.Hosting.Tests; @@ -147,19 +149,19 @@ public async Task ExpressionResolverGeneratesCorrectEndpointStrings(string exprN } [Theory] - [InlineData(false, "http://localhost:18889", "http://localhost:18889")] - [InlineData(true, "http://localhost:18889", "http://aspire.dev.internal:18889")] - [InlineData(false, "http://127.0.0.1:18889", "http://127.0.0.1:18889")] - [InlineData(true, "http://127.0.0.1:18889", "http://aspire.dev.internal:18889")] - [InlineData(false, "http://[::1]:18889", "http://[::1]:18889")] - [InlineData(true, "http://[::1]:18889", "http://aspire.dev.internal:18889")] - [InlineData(false, "Server=localhost,1433;User ID=sa;Password=xxx;Database=yyy", "Server=localhost,1433;User ID=sa;Password=xxx;Database=yyy")] - [InlineData(true, "Server=localhost,1433;User ID=sa;Password=xxx;Database=yyy", "Server=aspire.dev.internal,1433;User ID=sa;Password=xxx;Database=yyy")] - [InlineData(false, "Server=127.0.0.1,1433;User ID=sa;Password=xxx;Database=yyy", "Server=127.0.0.1,1433;User ID=sa;Password=xxx;Database=yyy")] - [InlineData(true, "Server=127.0.0.1,1433;User ID=sa;Password=xxx;Database=yyy", "Server=aspire.dev.internal,1433;User ID=sa;Password=xxx;Database=yyy")] - [InlineData(false, "Server=[::1],1433;User ID=sa;Password=xxx;Database=yyy", "Server=[::1],1433;User ID=sa;Password=xxx;Database=yyy")] - [InlineData(true, "Server=[::1],1433;User ID=sa;Password=xxx;Database=yyy", "Server=aspire.dev.internal,1433;User ID=sa;Password=xxx;Database=yyy")] - public async Task HostUrlPropertyGetsResolved(bool targetIsContainer, string hostUrlVal, string expectedValue) + [InlineData(false, true, "http://localhost:18889", "http://localhost:18889")] + [InlineData(true, true, "http://localhost:18889", "http://aspire.dev.internal:18889")] + [InlineData(false, true, "http://127.0.0.1:18889", "http://127.0.0.1:18889")] + [InlineData(true, true, "http://127.0.0.1:18889", "http://aspire.dev.internal:18889")] + [InlineData(false, true, "http://[::1]:18889", "http://[::1]:18889")] + [InlineData(true, true, "http://[::1]:18889", "http://aspire.dev.internal:18889")] + [InlineData(false, false, "http://localhost:18889", "http://localhost:18889")] + [InlineData(true, false, "http://localhost:18889", "http://host.docker.internal:18889")] + [InlineData(false, false, "http://127.0.0.1:18889", "http://127.0.0.1:18889")] + [InlineData(true, false, "http://127.0.0.1:18889", "http://host.docker.internal:18889")] + [InlineData(false, false, "http://[::1]:18889", "http://[::1]:18889")] + [InlineData(true, false, "http://[::1]:18889", "http://host.docker.internal:18889")] + public async Task HostUrlPropertyGetsResolved(bool targetIsContainer, bool withTunnel, string hostUrlVal, string expectedValue) { var builder = DistributedApplication.CreateBuilder(); @@ -176,14 +178,20 @@ public async Task HostUrlPropertyGetsResolved(bool targetIsContainer, string hos test = test.WithImage("someimage"); } - var config = await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(test.Resource, DistributedApplicationOperation.Run, TestServiceProvider.Instance).DefaultTimeout(); + var testServiceProvider = new TestServiceProvider(); + testServiceProvider.AddService(Options.Create(new DcpOptions() { EnableAspireContainerTunnel = withTunnel })); + testServiceProvider.AddService(new DistributedApplicationModel(builder.Resources)); + + var config = await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(test.Resource, DistributedApplicationOperation.Run, testServiceProvider).DefaultTimeout(); Assert.Equal(expectedValue, config["envname"]); } [Theory] - [InlineData(false, "http://localhost:18889")] - [InlineData(true, "http://aspire.dev.internal:18889")] - public async Task HostUrlPropertyGetsResolvedInOtlpExporterEndpoint(bool container, string expectedValue) + [InlineData(false, true, "http://localhost:18889")] + [InlineData(true, true, "http://aspire.dev.internal:18889")] + [InlineData(false, false, "http://localhost:18889")] + [InlineData(true, false, "http://host.docker.internal:18889")] + public async Task HostUrlPropertyGetsResolvedInOtlpExporterEndpoint(bool container, bool withTunnel, string expectedValue) { var builder = DistributedApplication.CreateBuilder(); @@ -195,7 +203,11 @@ public async Task HostUrlPropertyGetsResolvedInOtlpExporterEndpoint(bool contain test = test.WithImage("someimage"); } - var config = await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(test.Resource, DistributedApplicationOperation.Run, TestServiceProvider.Instance).DefaultTimeout(); + var testServiceProvider = new TestServiceProvider(); + testServiceProvider.AddService(Options.Create(new DcpOptions() { EnableAspireContainerTunnel = withTunnel })); + testServiceProvider.AddService(new DistributedApplicationModel(builder.Resources)); + + var config = await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(test.Resource, DistributedApplicationOperation.Run, testServiceProvider).DefaultTimeout(); Assert.Equal(expectedValue, config["OTEL_EXPORTER_OTLP_ENDPOINT"]); } @@ -246,9 +258,9 @@ sealed class TestValueProviderResource(string name) : Resource(name), IValueProv sealed class TestExpressionResolverResource : ContainerResource, IResourceWithEndpoints, IResourceWithConnectionString { readonly string _exprName; - EndpointReference Endpoint1 => new(this, "endpoint1", KnownNetworkIdentifiers.LocalhostNetwork); - EndpointReference Endpoint2 => new(this, "endpoint2", KnownNetworkIdentifiers.LocalhostNetwork); - EndpointReference Endpoint3 => new(this, "endpoint3", KnownNetworkIdentifiers.LocalhostNetwork); + EndpointReference Endpoint1 => new(this, "endpoint1"); + EndpointReference Endpoint2 => new(this, "endpoint2"); + EndpointReference Endpoint3 => new(this, "endpoint3"); Dictionary Expressions { get; } public TestExpressionResolverResource(string exprName) : base("testresource") { diff --git a/tests/Aspire.Hosting.Tests/Utils/EnvironmentVariableEvaluator.cs b/tests/Aspire.Hosting.Tests/Utils/EnvironmentVariableEvaluator.cs index 0ccb7636505..7eef30da6df 100644 --- a/tests/Aspire.Hosting.Tests/Utils/EnvironmentVariableEvaluator.cs +++ b/tests/Aspire.Hosting.Tests/Utils/EnvironmentVariableEvaluator.cs @@ -11,8 +11,7 @@ public static class EnvironmentVariableEvaluator public static async ValueTask> GetEnvironmentVariablesAsync( IResource resource, DistributedApplicationOperation applicationOperation = DistributedApplicationOperation.Run, - IServiceProvider? serviceProvider = null, - NetworkIdentifier? networkContext = null) + IServiceProvider? serviceProvider = null) { var executionContext = new DistributedApplicationExecutionContext(new DistributedApplicationExecutionContextOptions(applicationOperation) { @@ -35,8 +34,7 @@ await resource.ProcessEnvironmentVariableValuesAsync( } }, NullLogger.Instance, - CancellationToken.None, - networkContext); + CancellationToken.None); return environmentVariables; } diff --git a/tests/Aspire.Hosting.Yarp.Tests/AddYarpTests.cs b/tests/Aspire.Hosting.Yarp.Tests/AddYarpTests.cs index ed0657e50a5..a3c8b977068 100644 --- a/tests/Aspire.Hosting.Yarp.Tests/AddYarpTests.cs +++ b/tests/Aspire.Hosting.Yarp.Tests/AddYarpTests.cs @@ -3,8 +3,10 @@ using System.Security.Cryptography.X509Certificates; using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Dcp; using Aspire.Hosting.Tests.Utils; using Aspire.Hosting.Utils; +using Microsoft.Extensions.Options; namespace Aspire.Hosting.Yarp.Tests; @@ -37,6 +39,7 @@ public async Task VerifyRunEnvVariablesAreSet(bool containerCertificateSupport) new List(), containerCertificateSupport, trustCertificate: true)); + testProvider.AddService(Options.Create(new DcpOptions())); var yarp = builder.AddYarp("yarp"); @@ -86,6 +89,7 @@ public async Task VerifyWithStaticFilesAddsEnvironmentVariable() new List(), supportsContainerTrust: false, trustCertificate: true)); + testProvider.AddService(Options.Create(new DcpOptions())); var yarp = builder.AddYarp("yarp").WithStaticFiles(); @@ -130,6 +134,7 @@ public async Task VerifyWithStaticFilesBindMountAddsEnvironmentVariable() new List(), supportsContainerTrust: false, trustCertificate: true)); + testProvider.AddService(Options.Create(new DcpOptions())); using var tempDir = new TempDirectory();