diff --git a/src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs b/src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs
index a4009d690bc..8ee659e3040 100644
--- a/src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs
+++ b/src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs
@@ -244,6 +244,7 @@ public AllocatedEndpoint? AllocatedEndpoint
///
/// AllocatedEndpoint snapshot
/// The ID of the network that is associated with the AllocatedEndpoint snapshot.
+[DebuggerDisplay("NetworkID = {NetworkID}, Endpoint = {Snapshot}")]
public record class NetworkEndpointSnapshot(ValueSnapshot Snapshot, NetworkIdentifier NetworkID);
///
@@ -251,6 +252,7 @@ public record class NetworkEndpointSnapshot(ValueSnapshot Sna
///
public class NetworkEndpointSnapshotList : IEnumerable
{
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
private readonly ConcurrentBag _snapshots = new();
///
diff --git a/src/Aspire.Hosting/ApplicationModel/ExpressionResolver.cs b/src/Aspire.Hosting/ApplicationModel/ExpressionResolver.cs
index b430a9c7b47..c483a8a2360 100644
--- a/src/Aspire.Hosting/ApplicationModel/ExpressionResolver.cs
+++ b/src/Aspire.Hosting/ApplicationModel/ExpressionResolver.cs
@@ -8,32 +8,6 @@ namespace Aspire.Hosting.ApplicationModel;
internal class ExpressionResolver(CancellationToken cancellationToken)
{
-
- async Task ResolveInContainerContextAsync(EndpointReference endpointReference, EndpointProperty property, ValueProviderContext context)
- {
- // We need to use the root resource, e.g. AzureStorageResource instead of AzureBlobResource
- // Otherwise, we get the wrong values for IsContainer and Name
- var target = endpointReference.Resource.GetRootResource();
-
- return (property, target.IsContainer()) switch
- {
- // If Container -> Container, we use .dev.internal as host, and target port as port
- // This assumes both containers are on the same container network.
- // Different networks will require addtional routing/tunneling that we do not support today.
- (EndpointProperty.Host or EndpointProperty.IPV4Host, true) => $"{target.Name}.dev.internal",
- (EndpointProperty.Port, true) => await endpointReference.Property(EndpointProperty.TargetPort).GetValueAsync(context, cancellationToken).ConfigureAwait(false),
-
- (EndpointProperty.Url, _) => string.Format(CultureInfo.InvariantCulture, "{0}://{1}:{2}",
- endpointReference.Scheme,
- await ResolveInContainerContextAsync(endpointReference, EndpointProperty.Host, context).ConfigureAwait(false),
- await ResolveInContainerContextAsync(endpointReference, EndpointProperty.Port, context).ConfigureAwait(false)),
- (EndpointProperty.HostAndPort, _) => string.Format(CultureInfo.InvariantCulture, "{0}:{1}",
- await ResolveInContainerContextAsync(endpointReference, EndpointProperty.Host, context).ConfigureAwait(false),
- await ResolveInContainerContextAsync(endpointReference, EndpointProperty.Port, context).ConfigureAwait(false)),
- _ => await endpointReference.Property(property).GetValueAsync(context, cancellationToken).ConfigureAwait(false)
- };
- }
-
async Task EvalExpressionAsync(ReferenceExpression expr, ValueProviderContext context)
{
// This logic is similar to ReferenceExpression.GetValueAsync, except that we recurse on
@@ -95,14 +69,11 @@ 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 er.ContextNetworkID == KnownNetworkIdentifiers.DefaultAspireContainerNetwork || (er.ContextNetworkID == null && networkContext == KnownNetworkIdentifiers.DefaultAspireContainerNetwork) => new ResolvedValue(await ResolveInContainerContextAsync(er, EndpointProperty.Url, context).ConfigureAwait(false), false),
- EndpointReferenceExpression ep when ep.Endpoint.ContextNetworkID == KnownNetworkIdentifiers.DefaultAspireContainerNetwork || (ep.Endpoint.ContextNetworkID == null && 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/ValueSnapshot.cs b/src/Aspire.Hosting/ApplicationModel/ValueSnapshot.cs
index 47081560cfe..003137933e9 100644
--- a/src/Aspire.Hosting/ApplicationModel/ValueSnapshot.cs
+++ b/src/Aspire.Hosting/ApplicationModel/ValueSnapshot.cs
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics;
+
namespace Aspire.Hosting.ApplicationModel;
///
@@ -12,6 +14,7 @@ namespace Aspire.Hosting.ApplicationModel;
///
/// Thread-safe for concurrent SetValue / SetException / GetValueAsync calls.
///
+[DebuggerDisplay("{Value= {DebuggerValue()}")]
public sealed class ValueSnapshot where T : notnull
{
private readonly TaskCompletionSource _firstValueTcs =
@@ -73,4 +76,5 @@ public void SetException(Exception exception)
}
}
}
+ private T? DebuggerValue() => IsValueSet ? _firstValueTcs.Task.Result : default;
}
diff --git a/src/Aspire.Hosting/Dcp/DcpExecutor.cs b/src/Aspire.Hosting/Dcp/DcpExecutor.cs
index 1d6b253359c..df379b2f253 100644
--- a/src/Aspire.Hosting/Dcp/DcpExecutor.cs
+++ b/src/Aspire.Hosting/Dcp/DcpExecutor.cs
@@ -977,6 +977,29 @@ private void AddAllocatedEndpointInfo(IEnumerable resourc
bindingMode,
targetPortExpression: $$$"""{{- portForServing "{{{svc.Metadata.Name}}}" -}}""",
KnownNetworkIdentifiers.LocalhostNetwork);
+
+ if (appResource.DcpResource is Container ctr && ctr.Spec.Networks is not null)
+ {
+ // Once container networks are fully supported, this should allocate endpoints on those networks
+ var containerNetwork = ctr.Spec.Networks.FirstOrDefault(n => n.Name == KnownNetworkIdentifiers.DefaultAspireContainerNetwork.Value);
+
+ if (containerNetwork is not null)
+ {
+ var port = sp.EndpointAnnotation.TargetPort!;
+
+ var allocatedEndpoint = new AllocatedEndpoint(
+ sp.EndpointAnnotation,
+ $"{sp.ModelResource.Name}.dev.internal",
+ (int)port,
+ EndpointBindingMode.SingleAddress,
+ targetPortExpression: $$$"""{{- portForServing "{{{svc.Metadata.Name}}}" -}}""",
+ KnownNetworkIdentifiers.DefaultAspireContainerNetwork
+ );
+ var snapshot = new ValueSnapshot();
+ snapshot.SetValue(allocatedEndpoint);
+ sp.EndpointAnnotation.AllAllocatedEndpoints.TryAdd(allocatedEndpoint.NetworkID, snapshot);
+ }
+ }
}
}
@@ -1040,6 +1063,7 @@ ts.Service is not null &&
}
}
}
+
}
private void PrepareContainerNetworks()
diff --git a/tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs
index 53fe1bb9528..a7f55cc7a7a 100644
--- a/tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs
+++ b/tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs
@@ -368,7 +368,13 @@ public async Task WithPostgresMcpOnAzureDatabaseRunAsContainerAddsMcpResource()
.WithPasswordAuthentication(userName: user, password: pass)
.RunAsContainer(c =>
{
- c.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 5432));
+ c.WithEndpoint("tcp", e =>
+ {
+ e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 5432);
+ var snapshot = new ValueSnapshot();
+ snapshot.SetValue(new AllocatedEndpoint(e, "postgres.dev.internal", 5432, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork));
+ e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot);
+ });
});
var db = postgres.AddDatabase("db")
diff --git a/tests/Aspire.Hosting.Containers.Tests/ContainerResourceTests.cs b/tests/Aspire.Hosting.Containers.Tests/ContainerResourceTests.cs
index 335bbcac08b..06495623fc2 100644
--- a/tests/Aspire.Hosting.Containers.Tests/ContainerResourceTests.cs
+++ b/tests/Aspire.Hosting.Containers.Tests/ContainerResourceTests.cs
@@ -102,7 +102,7 @@ public async Task AddContainerWithArgs()
e.AllocatedEndpoint = new(e, "localhost", 1234, targetPortExpression: "1234");
// For container-container lookup we need to add an AllocatedEndpoint on the container network side
- var ccae = new AllocatedEndpoint(e, KnownHostNames.DefaultContainerTunnelHostName, 2234, EndpointBindingMode.SingleAddress, targetPortExpression: "2234", KnownNetworkIdentifiers.DefaultAspireContainerNetwork);
+ var ccae = new AllocatedEndpoint(e, "c1.dev.internal", 2234, EndpointBindingMode.SingleAddress, targetPortExpression: "2234", KnownNetworkIdentifiers.DefaultAspireContainerNetwork);
var snapshot = new ValueSnapshot();
snapshot.SetValue(ccae);
e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot);
@@ -114,7 +114,7 @@ public async Task AddContainerWithArgs()
e.UriScheme = "http";
// We only care about the container-side endpoint for this test
var snapshot = new ValueSnapshot();
- var ae = new AllocatedEndpoint(e, "localhost", 5678, EndpointBindingMode.SingleAddress, targetPortExpression: "5678", KnownNetworkIdentifiers.DefaultAspireContainerNetwork);
+ var ae = new AllocatedEndpoint(e, "container.dev.internal", 5678, EndpointBindingMode.SingleAddress, targetPortExpression: "5678", KnownNetworkIdentifiers.DefaultAspireContainerNetwork);
snapshot.SetValue(ae);
e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot);
})
diff --git a/tests/Aspire.Hosting.Milvus.Tests/AddMilvusTests.cs b/tests/Aspire.Hosting.Milvus.Tests/AddMilvusTests.cs
index 474e5458d2c..433b15b1a46 100644
--- a/tests/Aspire.Hosting.Milvus.Tests/AddMilvusTests.cs
+++ b/tests/Aspire.Hosting.Milvus.Tests/AddMilvusTests.cs
@@ -96,7 +96,13 @@ public async Task MilvusClientAppWithReferenceContainsConnectionStrings()
var pass = appBuilder.AddParameter("apikey", "pass");
var milvus = appBuilder.AddMilvus("my-milvus", pass)
- .WithEndpoint("grpc", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", MilvusPortGrpc));
+ .WithEndpoint("grpc", e =>
+ {
+ e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", MilvusPortGrpc);
+ var snapshot = new ValueSnapshot();
+ snapshot.SetValue(new AllocatedEndpoint(e, "my-milvus.dev.internal", MilvusPortGrpc, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork));
+ e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot);
+ });
var projectA = appBuilder.AddProject("projecta", o => o.ExcludeLaunchProfile = true)
.WithReference(milvus);
diff --git a/tests/Aspire.Hosting.PostgreSQL.Tests/PostgresMcpBuilderTests.cs b/tests/Aspire.Hosting.PostgreSQL.Tests/PostgresMcpBuilderTests.cs
index 42272057861..e2eb3b0de6d 100644
--- a/tests/Aspire.Hosting.PostgreSQL.Tests/PostgresMcpBuilderTests.cs
+++ b/tests/Aspire.Hosting.PostgreSQL.Tests/PostgresMcpBuilderTests.cs
@@ -75,7 +75,13 @@ public async Task WithPostgresMcpOnDatabaseSetsDatabaseUriEnvironmentVariable()
var pass = appBuilder.AddParameter("pass", "p@ssw0rd1");
appBuilder.AddPostgres("postgres", password: pass)
- .WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 5432))
+ .WithEndpoint("tcp", e =>
+ {
+ e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 5432);
+ var snapshot = new ValueSnapshot();
+ snapshot.SetValue(new AllocatedEndpoint(e, "postgres.dev.internal", 5432, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork));
+ e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot);
+ })
.AddDatabase("db")
.WithPostgresMcp();
diff --git a/tests/Aspire.Hosting.Qdrant.Tests/AddQdrantTests.cs b/tests/Aspire.Hosting.Qdrant.Tests/AddQdrantTests.cs
index c9d2a819290..4d50b6207c1 100644
--- a/tests/Aspire.Hosting.Qdrant.Tests/AddQdrantTests.cs
+++ b/tests/Aspire.Hosting.Qdrant.Tests/AddQdrantTests.cs
@@ -169,8 +169,20 @@ public async Task QdrantClientAppWithReferenceContainsConnectionStrings()
var pass = appBuilder.AddParameter("pass", "pass");
var qdrant = appBuilder.AddQdrant("my-qdrant", pass)
- .WithEndpoint("grpc", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 6334))
- .WithEndpoint("http", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 6333));
+ .WithEndpoint("grpc", e =>
+ {
+ e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 6334);
+ var snapshot = new ValueSnapshot();
+ snapshot.SetValue(new AllocatedEndpoint(e, "my-qdrant.dev.internal", 6334, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork));
+ e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot);
+ })
+ .WithEndpoint("http", e =>
+ {
+ e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 6333);
+ var snapshot = new ValueSnapshot();
+ snapshot.SetValue(new AllocatedEndpoint(e, "my-qdrant.dev.internal", 6333, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork));
+ e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot);
+ });
var projectA = appBuilder.AddProject("projecta", o => o.ExcludeLaunchProfile = true)
.WithReference(qdrant);
diff --git a/tests/Aspire.Hosting.Redis.Tests/AddRedisTests.cs b/tests/Aspire.Hosting.Redis.Tests/AddRedisTests.cs
index 362b9079ea7..cace9589d26 100644
--- a/tests/Aspire.Hosting.Redis.Tests/AddRedisTests.cs
+++ b/tests/Aspire.Hosting.Redis.Tests/AddRedisTests.cs
@@ -301,8 +301,27 @@ public async Task WithRedisInsightProducesCorrectEnvironmentVariables()
using var app = builder.Build();
// Add fake allocated endpoints.
- redis1.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 5001));
- redis2.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 5002));
+ redis1.WithEndpoint("tcp", e =>
+ {
+ e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 5001);
+ var snapshot = new ValueSnapshot();
+ snapshot.SetValue(new AllocatedEndpoint(e, "myredis1.dev.internal", 5001, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork));
+ e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot);
+ });
+ redis2.WithEndpoint("tcp", e =>
+ {
+ e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 5002);
+ var snapshot = new ValueSnapshot();
+ snapshot.SetValue(new AllocatedEndpoint(e, "myredis2.dev.internal", 5002, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork));
+ e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot);
+ });
+ redis3.WithEndpoint("tcp", e =>
+ {
+ e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 5003);
+ var snapshot = new ValueSnapshot();
+ snapshot.SetValue(new AllocatedEndpoint(e, "myredis3.dev.internal", 5003, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork));
+ e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot);
+ });
var redisInsight = Assert.Single(builder.Resources.OfType());
var envs = await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(redisInsight);
@@ -367,7 +386,6 @@ public async Task WithRedisInsightProducesCorrectEnvironmentVariables()
Assert.Equal("RI_REDIS_ALIAS3", item.Key);
Assert.Equal(redis3.Resource.Name, item.Value);
});
-
}
[Fact]
@@ -713,7 +731,13 @@ public async Task RedisInsightEnvironmentCallbackIsIdempotent()
using var appBuilder = TestDistributedApplicationBuilder.CreateWithTestContainerRegistry(testOutputHelper);
var redis = appBuilder.AddRedis("redis")
- .WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 6379))
+ .WithEndpoint("tcp", e =>
+ {
+ e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 6379);
+ var snapshot = new ValueSnapshot();
+ snapshot.SetValue(new AllocatedEndpoint(e, "redis.dev.internal", 6379, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork));
+ e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot);
+ })
.WithRedisInsight();
using var app = appBuilder.Build();
diff --git a/tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs b/tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs
index 8840c964dd1..8ccc1004794 100644
--- a/tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs
+++ b/tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs
@@ -1000,7 +1000,7 @@ public async Task EndpointPortsConainerProxiedNoPortTargetPortSet()
}
[Fact]
- public async Task EndpointPortsConainerProxiedPortAndTargetPortSet()
+ public async Task EndpointPortsContainerProxiedPortAndTargetPortSet()
{
var builder = DistributedApplication.CreateBuilder();
@@ -2175,6 +2175,105 @@ public async Task ProjectExecutable_NoSupportsDebuggingAnnotation_RunsInProcessM
Assert.Equal(ExecutionType.Process, exe.Spec.ExecutionType);
}
+ [Theory]
+ [InlineData(true, null, "aspire.dev.internal")]
+ [InlineData(false, null, "host.docker.internal")]
+ [InlineData(true, "super.star", "aspire.dev.internal")]
+ [InlineData(false, "mega.mushroom", "mega.mushroom")]
+ public async Task EndpointsAllocatedCorrectly(bool useTunnel, string? containerHostName, string expectedContainerHost)
+ {
+ var builder = DistributedApplication.CreateBuilder();
+ var executable = builder.AddExecutable("anExecutable", "command", "")
+ .WithEndpoint(name: "proxied", targetPort: 1234, port: 5678, isProxied: true)
+ .WithEndpoint(name: "notProxied", port: 8765, isProxied: false);
+
+ var container = builder.AddContainer("aContainer", "image")
+ .WithEndpoint(name: "proxied", port: 15678, targetPort: 11234, isProxied: true)
+ .WithEndpoint(name: "notProxied", port: 18765, isProxied: false);
+
+ var containerWithAlias = builder.AddContainer("containerWithAlias", "image")
+ .WithEndpoint(name: "proxied", port: 25678, targetPort: 21234, isProxied: true)
+ .WithEndpoint(name: "notProxied", port: 28765, isProxied: false)
+ .WithContainerNetworkAlias("custom.alias");
+
+ var kubernetesService = new TestKubernetesService();
+ using var app = builder.Build();
+ var distributedAppModel = app.Services.GetRequiredService();
+
+ var configDict = new Dictionary
+ {
+ ["AppHost:ContainerHostname"] = containerHostName
+ };
+ var configuration = new ConfigurationBuilder().AddInMemoryCollection(configDict).Build();
+
+ var dcpOptions = new DcpOptions
+ {
+ EnableAspireContainerTunnel = useTunnel,
+ };
+
+ var appExecutor = CreateAppExecutor(distributedAppModel, kubernetesService: kubernetesService, configuration: configuration, dcpOptions: dcpOptions);
+
+ await appExecutor.RunApplicationAsync();
+
+ await AssertEndpoint(executable.Resource, "proxied", KnownNetworkIdentifiers.LocalhostNetwork, KnownHostNames.Localhost, 5678);
+ await AssertEndpoint(executable.Resource, "notProxied", KnownNetworkIdentifiers.LocalhostNetwork, KnownHostNames.Localhost, 8765);
+
+ if (useTunnel)
+ {
+ await AssertTunneledPort(executable.Resource, "proxied");
+ await AssertTunneledPort(executable.Resource, "notProxied");
+
+ async ValueTask AssertTunneledPort(IResourceWithEndpoints resource, string endpointName)
+ {
+ var svcs = kubernetesService.CreatedResources
+ .OfType()
+ .Where(x => x.AppModelResourceName == resource.Name
+ && x.EndpointName == endpointName
+ && x.Metadata.Annotations.ContainsKey(CustomResource.ContainerTunnelInstanceName))
+ .ToList();
+
+ var svc = svcs.Single();
+
+ int port = svc.AllocatedPort!.Value;
+ await AssertEndpoint(executable.Resource, endpointName, KnownNetworkIdentifiers.DefaultAspireContainerNetwork, expectedContainerHost, port);
+ }
+ }
+ else
+ {
+ await AssertEndpoint(executable.Resource, "proxied", KnownNetworkIdentifiers.DefaultAspireContainerNetwork, expectedContainerHost, 5678);
+ await AssertEndpoint(executable.Resource, "notProxied", KnownNetworkIdentifiers.DefaultAspireContainerNetwork, expectedContainerHost, 8765);
+ }
+
+ await AssertEndpoint(container.Resource, "proxied", KnownNetworkIdentifiers.LocalhostNetwork, KnownHostNames.Localhost, 15678);
+ await AssertEndpoint(container.Resource, "notProxied", KnownNetworkIdentifiers.LocalhostNetwork, KnownHostNames.Localhost, 18765);
+
+ await AssertEndpoint(container.Resource, "proxied", KnownNetworkIdentifiers.DefaultAspireContainerNetwork, $"{container.Resource.Name}.dev.internal", 11234);
+ await AssertEndpoint(container.Resource, "notProxied", KnownNetworkIdentifiers.DefaultAspireContainerNetwork, $"{container.Resource.Name}.dev.internal", 18765);
+
+ await AssertEndpoint(containerWithAlias.Resource, "proxied", KnownNetworkIdentifiers.LocalhostNetwork, KnownHostNames.Localhost, 25678);
+ await AssertEndpoint(containerWithAlias.Resource, "notProxied", KnownNetworkIdentifiers.LocalhostNetwork, KnownHostNames.Localhost, 28765);
+
+ await AssertEndpoint(containerWithAlias.Resource, "proxied", KnownNetworkIdentifiers.DefaultAspireContainerNetwork, $"{containerWithAlias.Resource.Name}.dev.internal", 21234);
+ await AssertEndpoint(containerWithAlias.Resource, "notProxied", KnownNetworkIdentifiers.DefaultAspireContainerNetwork, $"{containerWithAlias.Resource.Name}.dev.internal", 28765);
+
+ async ValueTask AssertEndpoint(IResourceWithEndpoints resource, string name, NetworkIdentifier network, string address, int port)
+ {
+ var endpoint = resource.GetEndpoint(name).EndpointAnnotation;
+ var allocatedEndpoints = endpoint.AllAllocatedEndpoints;
+
+ Assert.Contains(allocatedEndpoints, a => a.NetworkID == network);
+
+ var allocatedEndpoint = await endpoint.AllAllocatedEndpoints.Single(x => x.NetworkID == network).Snapshot.GetValueAsync().DefaultTimeout();
+
+ Assert.Equal(endpoint, allocatedEndpoint.Endpoint);
+ Assert.Equal(address, allocatedEndpoint.Address);
+ Assert.Equal(EndpointBindingMode.SingleAddress, allocatedEndpoint.BindingMode);
+ Assert.Equal(port, allocatedEndpoint.Port);
+ Assert.Equal(endpoint.UriScheme, allocatedEndpoint.UriScheme);
+ Assert.Equal($"{address}:{port}", allocatedEndpoint.EndPointString);
+ }
+ }
+
private static void HasKnownCommandAnnotations(IResource resource)
{
var commandAnnotations = resource.Annotations.OfType().ToList();
diff --git a/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs b/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs
index f7fd6771288..11e3e3d7de6 100644
--- a/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs
+++ b/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs
@@ -285,6 +285,90 @@ public void TargetPort_ReturnsNullWhenNotDefined()
Assert.Null(targetPort);
}
+ [Theory]
+ [InlineData(EndpointProperty.Url, ResourceKind.Host, ResourceKind.Host, "blah://localhost:1234")]
+ [InlineData(EndpointProperty.Url, ResourceKind.Host, ResourceKind.Container, "blah://localhost:1234")]
+ [InlineData(EndpointProperty.Url, ResourceKind.Container, ResourceKind.Host, "blah://host.docker.internal:1234")]
+ [InlineData(EndpointProperty.Url, ResourceKind.Container, ResourceKind.Container, "blah://destination.dev.internal:4567")]
+ [InlineData(EndpointProperty.Host, ResourceKind.Host, ResourceKind.Host, "localhost")]
+ [InlineData(EndpointProperty.Host, ResourceKind.Host, ResourceKind.Container, "localhost")]
+ [InlineData(EndpointProperty.Host, ResourceKind.Container, ResourceKind.Host, "host.docker.internal")]
+ [InlineData(EndpointProperty.Host, ResourceKind.Container, ResourceKind.Container, "destination.dev.internal")]
+ [InlineData(EndpointProperty.IPV4Host, ResourceKind.Host, ResourceKind.Host, "127.0.0.1")]
+ [InlineData(EndpointProperty.IPV4Host, ResourceKind.Host, ResourceKind.Container, "127.0.0.1")]
+ [InlineData(EndpointProperty.IPV4Host, ResourceKind.Container, ResourceKind.Host, "host.docker.internal")]
+ [InlineData(EndpointProperty.IPV4Host, ResourceKind.Container, ResourceKind.Container, "destination.dev.internal")]
+ [InlineData(EndpointProperty.Port, ResourceKind.Host, ResourceKind.Host, "1234")]
+ [InlineData(EndpointProperty.Port, ResourceKind.Host, ResourceKind.Container, "1234")]
+ [InlineData(EndpointProperty.Port, ResourceKind.Container, ResourceKind.Host, "1234")]
+ [InlineData(EndpointProperty.Port, ResourceKind.Container, ResourceKind.Container, "4567")]
+ [InlineData(EndpointProperty.Scheme, ResourceKind.Host, ResourceKind.Host, "blah")]
+ [InlineData(EndpointProperty.Scheme, ResourceKind.Host, ResourceKind.Container, "blah")]
+ [InlineData(EndpointProperty.Scheme, ResourceKind.Container, ResourceKind.Host, "blah")]
+ [InlineData(EndpointProperty.Scheme, ResourceKind.Container, ResourceKind.Container, "blah")]
+ [InlineData(EndpointProperty.HostAndPort, ResourceKind.Host, ResourceKind.Host, "localhost:1234")]
+ [InlineData(EndpointProperty.HostAndPort, ResourceKind.Host, ResourceKind.Container, "localhost:1234")]
+ [InlineData(EndpointProperty.HostAndPort, ResourceKind.Container, ResourceKind.Host, "host.docker.internal:1234")]
+ [InlineData(EndpointProperty.HostAndPort, ResourceKind.Container, ResourceKind.Container, "destination.dev.internal:4567")]
+ public async Task PropertyResolutionTest(EndpointProperty property, ResourceKind sourceKind, ResourceKind destinationKind, object expectedResult)
+ {
+ int port = 1234;
+ int targetPort = 4567;
+
+ var source = CreateResource("caller", sourceKind);
+ var destination = CreateResource("destination", destinationKind);
+
+ var network = source.GetDefaultResourceNetwork();
+
+ // This logic is tightly coupled to how `DcpExecutor` allocates endpoints
+ var annotation = new EndpointAnnotation(ProtocolType.Tcp, uriScheme: "blah", name: "http");
+ annotation.AllocatedEndpoint = new(annotation, "localhost", port);
+ destination.Annotations.Add(annotation);
+
+ (string containerHost, int containerPort) = destination.IsContainer()
+ ? ("destination.dev.internal", targetPort)
+ : ("host.docker.internal", port);
+
+ var containerEndpoint = new AllocatedEndpoint(annotation, containerHost, containerPort, EndpointBindingMode.SingleAddress, targetPortExpression: targetPort.ToString(), KnownNetworkIdentifiers.DefaultAspireContainerNetwork);
+ var snapshot = new ValueSnapshot();
+ snapshot.SetValue(containerEndpoint);
+ annotation.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot);
+
+ var expression = destination.GetEndpoint(annotation.Name).Property(property);
+
+ var resultFromCaller = await expression.GetValueAsync(new ValueProviderContext
+ {
+ Caller = source
+ });
+ Assert.Equal(expectedResult, resultFromCaller);
+
+ var resultFromNetwork = await expression.GetValueAsync(new ValueProviderContext
+ {
+ Network = network
+ });
+ Assert.Equal(expectedResult, resultFromNetwork);
+
+ static IResourceWithEndpoints CreateResource(string name, ResourceKind kind)
+ {
+ if (kind == ResourceKind.Container)
+ {
+ var resource = new TestResource(name);
+ resource.Annotations.Add(new ContainerImageAnnotation { Image = "test-image" });
+ return resource;
+ }
+ else
+ {
+ return new TestResource(name);
+ }
+ }
+ }
+
+ public enum ResourceKind
+ {
+ Host,
+ Container
+ }
+
private sealed class TestResource(string name) : Resource(name), IResourceWithEndpoints
{
}
diff --git a/tests/Aspire.Hosting.Tests/ExpressionResolverTests.cs b/tests/Aspire.Hosting.Tests/ExpressionResolverTests.cs
index 88bbcdd48fd..429757d98a8 100644
--- a/tests/Aspire.Hosting.Tests/ExpressionResolverTests.cs
+++ b/tests/Aspire.Hosting.Tests/ExpressionResolverTests.cs
@@ -84,6 +84,10 @@ public async Task ExpressionResolverGeneratesCorrectEndpointStrings(string exprN
{
var builder = DistributedApplication.CreateBuilder();
+ var containerHost = targetIsContainer
+ ? "testresource.dev.internal"
+ : KnownHostNames.DefaultContainerTunnelHostName;
+
var target = builder.AddResource(new TestExpressionResolverResource(exprName))
.WithEndpoint("endpoint1", e =>
{
@@ -92,31 +96,31 @@ public async Task ExpressionResolverGeneratesCorrectEndpointStrings(string exprN
if (sourceIsContainer)
{
// Note: on the container network side the port and target port are always the same for AllocatedEndpoint.
- var ae = new AllocatedEndpoint(e, KnownHostNames.DefaultContainerTunnelHostName, 22345, EndpointBindingMode.SingleAddress, targetPortExpression: "22345", KnownNetworkIdentifiers.DefaultAspireContainerNetwork);
+ var ae = new AllocatedEndpoint(e, containerHost, 22345, EndpointBindingMode.SingleAddress, targetPortExpression: "22345", KnownNetworkIdentifiers.DefaultAspireContainerNetwork);
var snapshot = new ValueSnapshot();
snapshot.SetValue(ae);
e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot);
}
})
.WithEndpoint("endpoint2", e =>
- {
- e.UriScheme = "https";
- e.AllocatedEndpoint = new(e, "localhost", 12346, targetPortExpression: "10001");
- if (sourceIsContainer)
- {
- var ae = new AllocatedEndpoint(e, KnownHostNames.DefaultContainerTunnelHostName, 22346, EndpointBindingMode.SingleAddress, targetPortExpression: "22346", KnownNetworkIdentifiers.DefaultAspireContainerNetwork);
- var snapshot = new ValueSnapshot();
- snapshot.SetValue(ae);
- e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot);
- }
- })
+ {
+ e.UriScheme = "https";
+ e.AllocatedEndpoint = new(e, "localhost", 12346, targetPortExpression: "10001");
+ if (sourceIsContainer)
+ {
+ var ae = new AllocatedEndpoint(e, containerHost, 22346, EndpointBindingMode.SingleAddress, targetPortExpression: "22346", KnownNetworkIdentifiers.DefaultAspireContainerNetwork);
+ var snapshot = new ValueSnapshot();
+ snapshot.SetValue(ae);
+ e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot);
+ }
+ })
.WithEndpoint("endpoint3", e =>
{
e.UriScheme = "https";
e.AllocatedEndpoint = new(e, "host with space", 12347);
if (sourceIsContainer)
{
- var ae = new AllocatedEndpoint(e, KnownHostNames.DefaultContainerTunnelHostName, 22347, EndpointBindingMode.SingleAddress, targetPortExpression: "22346", KnownNetworkIdentifiers.DefaultAspireContainerNetwork);
+ var ae = new AllocatedEndpoint(e, containerHost, 22347, EndpointBindingMode.SingleAddress, targetPortExpression: "22346", KnownNetworkIdentifiers.DefaultAspireContainerNetwork);
var snapshot = new ValueSnapshot();
snapshot.SetValue(ae);
e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot);
@@ -217,10 +221,18 @@ public async Task ContainerToContainerEndpointShouldResolve()
{
var builder = DistributedApplication.CreateBuilder();
+ var endpoint = new EndpointAnnotation(System.Net.Sockets.ProtocolType.Tcp, KnownNetworkIdentifiers.DefaultAspireContainerNetwork)
+ {
+ Name = "http",
+ UriScheme = "http",
+ Port = 8001,
+ TargetPort = 8080,
+ };
+ endpoint.AllocatedEndpoint = new(endpoint, "myContainer.dev.internal", (int)endpoint.TargetPort, EndpointBindingMode.SingleAddress, "{{ targetPort }}");
+
var connectionStringResource = builder.AddResource(new MyContainerResource("myContainer"))
.WithImage("redis")
- .WithHttpEndpoint(port: 8001, targetPort: 8080)
- .WithEndpoint("http", ep => ep.AllocatedEndpoint = new(ep, "localhost", 8001, EndpointBindingMode.SingleAddress, "{{ targetPort }}", KnownNetworkIdentifiers.LocalhostNetwork));
+ .WithAnnotation(endpoint);
var dep = builder.AddContainer("container", "redis")
.WithReference(connectionStringResource)
diff --git a/tests/Aspire.Hosting.Tests/WithEnvironmentTests.cs b/tests/Aspire.Hosting.Tests/WithEnvironmentTests.cs
index dd40af66a63..c6b9b0a1070 100644
--- a/tests/Aspire.Hosting.Tests/WithEnvironmentTests.cs
+++ b/tests/Aspire.Hosting.Tests/WithEnvironmentTests.cs
@@ -227,7 +227,12 @@ public async Task EnvironmentVariableExpressions()
.WithHttpEndpoint(name: "primary", targetPort: 10005)
.WithEndpoint("primary", ep =>
{
- ep.AllocatedEndpoint = new AllocatedEndpoint(ep, "localhost", 90);
+ ep.AllocatedEndpoint = new AllocatedEndpoint(ep, "localhost", 17454);
+
+ var ae = new AllocatedEndpoint(ep, "container1.dev.internal", 10005, EndpointBindingMode.SingleAddress, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork);
+ var snapshot = new ValueSnapshot();
+ snapshot.SetValue(ae);
+ ep.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot);
});
var endpoint = container.GetEndpoint("primary");