Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
39 changes: 38 additions & 1 deletion src/Aspire.Hosting/Dcp/ContainerCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ internal record struct HostResourceWithEndpoints(
/// </summary>
internal sealed class ContainerCreator : IObjectCreator<Container, ContainerCreationContext>, IObjectCreator<ContainerExec, EmptyCreationContext>
{
private const string ContainerTunnelContainerName = "aspire";

private readonly IConfiguration _configuration;
private readonly IOptions<DcpOptions> _options;
private readonly DcpNameGenerator _nameGenerator;
Expand Down Expand Up @@ -123,7 +125,9 @@ internal void PrepareContainerNetworks()

public IEnumerable<RenderedModelResource<Container>> PrepareObjects()
{
var modelContainerResources = _model.GetContainerResources();
var modelContainerResources = _model.GetContainerResources().ToArray();
ValidateContainerTunnelContainerNameConflicts(modelContainerResources);

var result = new List<RenderedModelResource<Container>>();

foreach (var container in modelContainerResources)
Expand Down Expand Up @@ -194,6 +198,39 @@ public IEnumerable<RenderedModelResource<Container>> PrepareObjects()
return result;
}

private void ValidateContainerTunnelContainerNameConflicts(IEnumerable<IResource> modelContainerResources)
{
if (!_options.Value.EnableAspireContainerTunnel)
{
return;
Comment thread
karolz-ms marked this conversation as resolved.
}

foreach (var container in modelContainerResources)
{
if (IsContainerTunnelContainerName(container.Name))
{
throw new DistributedApplicationException($"Container resource name '{container.Name}' conflicts with the Aspire container tunnel container name '{ContainerTunnelContainerName}'. Rename the resource or disable the Aspire container tunnel.");
}

if (container.TryGetLastAnnotation<ContainerNameAnnotation>(out var containerNameAnnotation) &&
IsContainerTunnelContainerName(containerNameAnnotation.Name))
{
throw new DistributedApplicationException($"Container resource '{container.Name}' uses container name '{containerNameAnnotation.Name}', which conflicts with the Aspire container tunnel container name '{ContainerTunnelContainerName}'. Rename the container or disable the Aspire container tunnel.");
Comment thread
karolz-ms marked this conversation as resolved.
}

foreach (var aliasAnnotation in container.Annotations.OfType<ContainerNetworkAliasAnnotation>())
{
if (IsContainerTunnelContainerName(aliasAnnotation.Alias))
{
throw new DistributedApplicationException($"Container resource '{container.Name}' uses network alias '{aliasAnnotation.Alias}', which conflicts with the Aspire container tunnel container name '{ContainerTunnelContainerName}'. Rename the alias or disable the Aspire container tunnel.");
}
}
}

static bool IsContainerTunnelContainerName(string name)
=> string.Equals(name, ContainerTunnelContainerName, StringComparison.OrdinalIgnoreCase);
}

public bool IsReadyToCreate(RenderedModelResource<Container> resource, ContainerCreationContext cctx)
{
// Containers are always "created" (submitted to DCP), they just get Spec.Start = false initially
Expand Down
90 changes: 90 additions & 0 deletions tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,96 @@ public async Task CreateContainer_ArgsResolvedInSnapshot_UsesEffectiveArgsFromCr
Assert.Equal(effectiveArgs, GetEnumerablePropertyValue<string>(snapshot, KnownProperties.Container.Args).ToArray());
}

[Theory]
[InlineData("aspire")]
[InlineData("ASPIRE")]
public async Task RunApplicationAsync_ThrowsWhenContainerResourceNameConflictsWithContainerTunnelName(string containerName)
{
var builder = DistributedApplication.CreateBuilder();

builder.AddContainer(containerName, "image");

using var app = builder.Build();
var distributedAppModel = app.Services.GetRequiredService<DistributedApplicationModel>();

var appExecutor = CreateAppExecutor(
distributedAppModel,
kubernetesService: new TestKubernetesService(),
dcpOptions: new DcpOptions { EnableAspireContainerTunnel = true });

var ex = await Assert.ThrowsAsync<DistributedApplicationException>(() => appExecutor.RunApplicationAsync());
Assert.Contains("container tunnel container name", ex.Message);
Comment thread
karolz-ms marked this conversation as resolved.
}

[Theory]
[InlineData("aspire")]
[InlineData("ASPIRE")]
public async Task RunApplicationAsync_ThrowsWhenExplicitContainerNameConflictsWithContainerTunnelName(string containerName)
{
var builder = DistributedApplication.CreateBuilder();

builder.AddContainer("aContainer", "image")
.WithContainerName(containerName);

using var app = builder.Build();
var distributedAppModel = app.Services.GetRequiredService<DistributedApplicationModel>();

var appExecutor = CreateAppExecutor(
distributedAppModel,
kubernetesService: new TestKubernetesService(),
dcpOptions: new DcpOptions { EnableAspireContainerTunnel = true });

var ex = await Assert.ThrowsAsync<DistributedApplicationException>(() => appExecutor.RunApplicationAsync());
Assert.Contains("container tunnel container name", ex.Message);
Comment thread
karolz-ms marked this conversation as resolved.
}

[Theory]
[InlineData("aspire")]
[InlineData("ASPIRE")]
public async Task RunApplicationAsync_ThrowsWhenNetworkAliasConflictsWithContainerTunnelName(string alias)
{
var builder = DistributedApplication.CreateBuilder();

builder.AddContainer("aContainer", "image")
.WithContainerNetworkAlias(alias);

using var app = builder.Build();
var distributedAppModel = app.Services.GetRequiredService<DistributedApplicationModel>();

var appExecutor = CreateAppExecutor(
distributedAppModel,
kubernetesService: new TestKubernetesService(),
dcpOptions: new DcpOptions { EnableAspireContainerTunnel = true });

var ex = await Assert.ThrowsAsync<DistributedApplicationException>(() => appExecutor.RunApplicationAsync());
Assert.Contains("container tunnel container name", ex.Message);
}

[Fact]
public async Task RunApplicationAsync_AllowsContainerNameMatchingContainerTunnelNameWhenContainerTunnelDisabled()
{
var builder = DistributedApplication.CreateBuilder();

builder.AddContainer("aspire", "image");
builder.AddContainer("aContainer", "image")
.WithContainerName("ASPIRE");
builder.AddContainer("bContainer", "image")
.WithContainerNetworkAlias("ASPIRE");

var kubernetesService = new TestKubernetesService();
using var app = builder.Build();
var distributedAppModel = app.Services.GetRequiredService<DistributedApplicationModel>();

var appExecutor = CreateAppExecutor(
distributedAppModel,
kubernetesService: kubernetesService,
dcpOptions: new DcpOptions { EnableAspireContainerTunnel = false });

await appExecutor.RunApplicationAsync();

Assert.Equal(3, kubernetesService.CreatedResources.OfType<Container>().Count());
}

[Fact]
public async Task ResourceRestarted_EnvironmentCallbacksApplied()
{
Expand Down
Loading