Skip to content

Commit

Permalink
Isolate the use of host.docker.internal (#2685)
Browse files Browse the repository at this point in the history
* Isolate the use of docker.host.internal
- For podman support we need to use another host name (host.containers.internal). That's configurable using a setting AppHost:ContainerHostName. Remove the hardcoded use of docker.host.internal and instead read from configuration everywhere.

* Fixed nit
  • Loading branch information
davidfowl authored Mar 6, 2024
1 parent 499440e commit 692dc41
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 46 deletions.
10 changes: 7 additions & 3 deletions src/Aspire.Hosting/MongoDB/MongoDBBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System.Net.Sockets;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.MongoDB;
using Aspire.Hosting.Utils;
using Microsoft.Extensions.Configuration;

namespace Aspire.Hosting;

Expand Down Expand Up @@ -66,18 +68,20 @@ public static IResourceBuilder<T> WithMongoExpress<T>(this IResourceBuilder<T> b
var mongoExpressContainer = new MongoExpressContainerResource(containerName);
builder.ApplicationBuilder.AddResource(mongoExpressContainer)
.WithAnnotation(new ContainerImageAnnotation { Image = "mongo-express", Tag = "1.0.2-20" })
.WithEnvironment(context => ConfigureMongoExpressContainer(context, builder.Resource))
.WithEnvironment(context => ConfigureMongoExpressContainer(builder.ApplicationBuilder.Configuration, context, builder.Resource))
.WithHttpEndpoint(containerPort: 8081, hostPort: hostPort, name: containerName)
.ExcludeFromManifest();

return builder;
}

private static void ConfigureMongoExpressContainer(EnvironmentCallbackContext context, IResource resource)
private static void ConfigureMongoExpressContainer(IConfiguration configuration, EnvironmentCallbackContext context, IResource resource)
{
var containerHostName = HostNameResolver.ReplaceLocalhostWithContainerHost("localhost", configuration);

var hostPort = GetResourcePort(resource);

context.EnvironmentVariables.Add("ME_CONFIG_MONGODB_URL", $"mongodb://host.docker.internal:{hostPort}/?directConnection=true");
context.EnvironmentVariables.Add("ME_CONFIG_MONGODB_URL", $"mongodb://{containerHostName}:{hostPort}/?directConnection=true");
context.EnvironmentVariables.Add("ME_CONFIG_BASICAUTH", "false");

static int GetResourcePort(IResource resource)
Expand Down
10 changes: 7 additions & 3 deletions src/Aspire.Hosting/MySql/PhpMyAdminConfigWriterHook.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@

using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Lifecycle;
using Aspire.Hosting.Utils;
using Microsoft.Extensions.Configuration;

namespace Aspire.Hosting.MySql;

internal class PhpMyAdminConfigWriterHook : IDistributedApplicationLifecycleHook
internal class PhpMyAdminConfigWriterHook(IConfiguration configuration) : IDistributedApplicationLifecycleHook
{
public Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken)
{
Expand All @@ -26,6 +28,8 @@ public Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, C
return Task.CompletedTask;
}

var containerHostName = HostNameResolver.ReplaceLocalhostWithContainerHost("localhost", configuration);

if (mySqlInstances.Count() == 1)
{
var singleInstance = mySqlInstances.Single();
Expand All @@ -34,7 +38,7 @@ public Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, C
var endpoint = allocatedEndPoints.Where(ae => ae.Name == "tcp").Single();
myAdminResource.Annotations.Add(new EnvironmentCallbackAnnotation((EnvironmentCallbackContext context) =>
{
context.EnvironmentVariables.Add("PMA_HOST", $"host.docker.internal:{endpoint.Port}");
context.EnvironmentVariables.Add("PMA_HOST", $"{containerHostName}:{endpoint.Port}");
context.EnvironmentVariables.Add("PMA_USER", "root");
context.EnvironmentVariables.Add("PMA_PASSWORD", singleInstance.Password);
}));
Expand All @@ -55,7 +59,7 @@ public Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, C
{
var endpoint = allocatedEndpoints.Where(ae => ae.Name == "tcp").Single();
writer.WriteLine("$i++;");
writer.WriteLine($"$cfg['Servers'][$i]['host'] = 'host.docker.internal:{endpoint.Port}';");
writer.WriteLine($"$cfg['Servers'][$i]['host'] = '{containerHostName}:{endpoint.Port}';");
writer.WriteLine($"$cfg['Servers'][$i]['verbose'] = '{mySqlInstance.Name}';");
writer.WriteLine($"$cfg['Servers'][$i]['auth_type'] = 'cookie';");
writer.WriteLine($"$cfg['Servers'][$i]['user'] = 'root';");
Expand Down
10 changes: 7 additions & 3 deletions src/Aspire.Hosting/Postgres/PgAdminConfigWriterHook.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
using System.Text.Json;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Lifecycle;
using Aspire.Hosting.Utils;
using Microsoft.Extensions.Configuration;

namespace Aspire.Hosting.Postgres;

internal class PgAdminConfigWriterHook : IDistributedApplicationLifecycleHook
internal class PgAdminConfigWriterHook(IConfiguration configuration) : IDistributedApplicationLifecycleHook
{
public Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken)
{
Expand All @@ -21,11 +23,13 @@ public Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, C
using var stream = new FileStream(serverFileMount.Source!, FileMode.Create);
using var writer = new Utf8JsonWriter(stream);

var serverIndex = 1;
var containerHostName = HostNameResolver.ReplaceLocalhostWithContainerHost("localhost", configuration);

writer.WriteStartObject();
writer.WriteStartObject("Servers");

var serverIndex = 1;

foreach (var postgresInstance in postgresInstances)
{
if (postgresInstance.TryGetAllocatedEndPoints(out var allocatedEndpoints))
Expand All @@ -35,7 +39,7 @@ public Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, C
writer.WriteStartObject($"{serverIndex}");
writer.WriteString("Name", postgresInstance.Name);
writer.WriteString("Group", "Aspire instances");
writer.WriteString("Host", "host.docker.internal");
writer.WriteString("Host", containerHostName);
writer.WriteNumber("Port", endpoint.Port);
writer.WriteString("Username", "postgres");
writer.WriteString("SSLMode", "prefer");
Expand Down
8 changes: 6 additions & 2 deletions src/Aspire.Hosting/Redis/RedisCommanderConfigWriterHook.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
using Aspire.Hosting.ApplicationModel;
using System.Text;
using Aspire.Hosting.Lifecycle;
using Microsoft.Extensions.Configuration;
using Aspire.Hosting.Utils;

namespace Aspire.Hosting.Redis;

internal class RedisCommanderConfigWriterHook : IDistributedApplicationLifecycleHook
internal class RedisCommanderConfigWriterHook(IConfiguration configuration) : IDistributedApplicationLifecycleHook
{
public Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken)
{
Expand All @@ -25,6 +27,8 @@ public Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, C
return Task.CompletedTask;
}

var containerHostName = HostNameResolver.ReplaceLocalhostWithContainerHost("localhost", configuration);

var hostsVariableBuilder = new StringBuilder();

foreach (var redisInstance in redisInstances)
Expand All @@ -33,7 +37,7 @@ public Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, C
{
var endpoint = allocatedEndpoints.Where(ae => ae.Name == "tcp").Single();

var hostString = $"{(hostsVariableBuilder.Length > 0 ? "," : string.Empty)}{redisInstance.Name}:host.docker.internal:{endpoint.Port}:0";
var hostString = $"{(hostsVariableBuilder.Length > 0 ? "," : string.Empty)}{redisInstance.Name}:{containerHostName}:{endpoint.Port}:0";
hostsVariableBuilder.Append(hostString);
}
}
Expand Down
40 changes: 35 additions & 5 deletions tests/Aspire.Hosting.Tests/MongoDB/AddMongoDBTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,43 @@ public void MongoDBCreatesConnectionString()
Assert.Equal("{mongodb.connectionString}/mydatabase", connectionStringResource.ConnectionStringExpression);
}

[Fact]
public void WithMongoExpressAddsContainer()
[Theory]
[InlineData(null, "host.docker.internal")]
[InlineData("host.containers.internal", "host.containers.internal")]
public void WithMongoExpressAddsContainer(string? containerHost, string expectedHost)
{
var builder = DistributedApplication.CreateBuilder();
builder.AddMongoDB("mongo").WithMongoExpress();

Assert.Single(builder.Resources.OfType<MongoExpressContainerResource>());
if (containerHost is not null)
{
builder.Configuration["AppHost:ContainerHostname"] = containerHost;
}

builder.AddMongoDB("mongo")
.WithAnnotation(new AllocatedEndpointAnnotation("tcp", ProtocolType.Tcp, "locahost", 3452, "tcp"))
.WithMongoExpress();

var mongoExpress = Assert.Single(builder.Resources.OfType<MongoExpressContainerResource>());

Assert.Equal("mongo-mongoexpress", mongoExpress.Name);
Assert.True(mongoExpress.TryGetEnvironmentVariables(out var environmentCallbackAnnotations));
var context = new EnvironmentCallbackContext(new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run)); ;
foreach (var annotation in environmentCallbackAnnotations)
{
annotation.Callback(context);
}

Assert.Collection(context.EnvironmentVariables,
kvp =>
{
Assert.Equal("ME_CONFIG_MONGODB_URL", kvp.Key);
Assert.Equal($"mongodb://{expectedHost}:3452/?directConnection=true", kvp.Value);
},
kvp =>
{
Assert.Equal("ME_CONFIG_BASICAUTH", kvp.Key);
Assert.Equal("false", kvp.Value);
});
}

[Fact]
Expand All @@ -130,7 +160,7 @@ public void VerifyManifest()

var mongoManifest = ManifestUtils.GetManifest(mongo.Resource);
var dbManifest = ManifestUtils.GetManifest(db.Resource);

Assert.Equal("container.v0", mongoManifest["type"]?.ToString());
Assert.Equal(mongo.Resource.ConnectionStringExpression, mongoManifest["connectionString"]?.ToString());

Expand Down
40 changes: 28 additions & 12 deletions tests/Aspire.Hosting.Tests/MySql/AddMySqlTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,18 +188,26 @@ public void WithMySqlTwiceEndsUpWithOneAdminContainer()
Assert.Single(builder.Resources.OfType<PhpMyAdminContainerResource>());
}

[Fact]
public async Task SingleMySqlInstanceProducesCorrectMySqlHostsVariable()
[Theory]
[InlineData(null, "host.docker.internal")]
[InlineData("host.containers.internal", "host.containers.internal")]
public async Task SingleMySqlInstanceProducesCorrectMySqlHostsVariable(string? containerHost, string expectedHost)
{
var builder = DistributedApplication.CreateBuilder();

if (containerHost is not null)
{
builder.Configuration["AppHost:ContainerHostname"] = containerHost;
}

var mysql = builder.AddMySql("mySql").WithPhpMyAdmin();
using var app = builder.Build();

// Add fake allocated endpoints.
mysql.WithAnnotation(new AllocatedEndpointAnnotation("tcp", ProtocolType.Tcp, "host.docker.internal", 5001, "tcp"));
mysql.WithAnnotation(new AllocatedEndpointAnnotation("tcp", ProtocolType.Tcp, "localhost", 5001, "tcp"));

var model = app.Services.GetRequiredService<DistributedApplicationModel>();
var hook = new PhpMyAdminConfigWriterHook();
var hook = new PhpMyAdminConfigWriterHook(builder.Configuration);
await hook.AfterEndpointsAllocatedAsync(model, CancellationToken.None);

var myAdmin = builder.Resources.Single(r => r.Name.EndsWith("-phpmyadmin"));
Expand All @@ -215,7 +223,7 @@ public async Task SingleMySqlInstanceProducesCorrectMySqlHostsVariable()
annotation.Callback(context);
}

Assert.Equal("host.docker.internal:5001", context.EnvironmentVariables["PMA_HOST"]);
Assert.Equal($"{expectedHost}:5001", context.EnvironmentVariables["PMA_HOST"]);
Assert.NotNull(context.EnvironmentVariables["PMA_USER"]);
Assert.NotNull(context.EnvironmentVariables["PMA_PASSWORD"]);
}
Expand All @@ -233,32 +241,40 @@ public void WithPhpMyAdminAddsContainer()
Assert.Equal("/etc/phpmyadmin/config.user.inc.php", volume.Target);
}

[Fact]
public void WithPhpMyAdminProducesValidServerConfigFile()
[Theory]
[InlineData(null, "host.docker.internal")]
[InlineData("host.containers.internal", "host.containers.internal")]
public void WithPhpMyAdminProducesValidServerConfigFile(string? containerHost, string expectedHost)
{
var builder = DistributedApplication.CreateBuilder();

if (containerHost is not null)
{
builder.Configuration["AppHost:ContainerHostname"] = containerHost;
}

var mysql1 = builder.AddMySql("mysql1").WithPhpMyAdmin(8081);
var mysql2 = builder.AddMySql("mysql2").WithPhpMyAdmin(8081);

// Add fake allocated endpoints.
mysql1.WithAnnotation(new AllocatedEndpointAnnotation("tcp", ProtocolType.Tcp, "host.docker.internal", 5001, "tcp"));
mysql2.WithAnnotation(new AllocatedEndpointAnnotation("tcp", ProtocolType.Tcp, "host.docker.internal", 5002, "tcp"));
mysql1.WithAnnotation(new AllocatedEndpointAnnotation("tcp", ProtocolType.Tcp, "localhost", 5001, "tcp"));
mysql2.WithAnnotation(new AllocatedEndpointAnnotation("tcp", ProtocolType.Tcp, "localhost", 5002, "tcp"));

var myAdmin = builder.Resources.Single(r => r.Name.EndsWith("-phpmyadmin"));
var volume = myAdmin.Annotations.OfType<ContainerMountAnnotation>().Single();

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

var hook = new PhpMyAdminConfigWriterHook();
var hook = new PhpMyAdminConfigWriterHook(builder.Configuration);
hook.AfterEndpointsAllocatedAsync(appModel, CancellationToken.None);

using var stream = File.OpenRead(volume.Source!);
var fileContents = new StreamReader(stream).ReadToEnd();

// check to see that the two hosts are in the file
string pattern1 = @"\$cfg\['Servers'\]\[\$i\]\['host'\] = 'host.docker.internal:5001';";
string pattern2 = @"\$cfg\['Servers'\]\[\$i\]\['host'\] = 'host.docker.internal:5002';";
string pattern1 = $@"\$cfg\['Servers'\]\[\$i\]\['host'\] = '{expectedHost}:5001';";
string pattern2 = $@"\$cfg\['Servers'\]\[\$i\]\['host'\] = '{expectedHost}:5002';";
Match match1 = Regex.Match(fileContents, pattern1);
Assert.True(match1.Success);
Match match2 = Regex.Match(fileContents, pattern2);
Expand Down
22 changes: 15 additions & 7 deletions tests/Aspire.Hosting.Tests/Postgres/AddPostgresTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -275,24 +275,32 @@ public void WithPostgresTwiceEndsUpWithOneContainer()
builder.Resources.Single(r => r.Name.EndsWith("-pgadmin"));
}

[Fact]
public void WithPostgresProducesValidServersJsonFile()
[Theory]
[InlineData(null, "host.docker.internal")]
[InlineData("host.containers.internal", "host.containers.internal")]
public void WithPostgresProducesValidServersJsonFile(string? containerHost, string expectedHost)
{
var builder = DistributedApplication.CreateBuilder();

if (containerHost is not null)
{
builder.Configuration["AppHost:ContainerHostname"] = containerHost;
}

var pg1 = builder.AddPostgres("mypostgres1").WithPgAdmin(8081);
var pg2 = builder.AddPostgres("mypostgres2").WithPgAdmin(8081);

// Add fake allocated endpoints.
pg1.WithAnnotation(new AllocatedEndpointAnnotation("tcp", ProtocolType.Tcp, "host.docker.internal", 5001, "tcp"));
pg2.WithAnnotation(new AllocatedEndpointAnnotation("tcp", ProtocolType.Tcp, "host.docker.internal", 5002, "tcp"));
pg1.WithAnnotation(new AllocatedEndpointAnnotation("tcp", ProtocolType.Tcp, "localhost", 5001, "tcp"));
pg2.WithAnnotation(new AllocatedEndpointAnnotation("tcp", ProtocolType.Tcp, "localhost", 5002, "tcp"));

var pgadmin = builder.Resources.Single(r => r.Name.EndsWith("-pgadmin"));
var volume = pgadmin.Annotations.OfType<ContainerMountAnnotation>().Single();

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

var hook = new PgAdminConfigWriterHook();
var hook = new PgAdminConfigWriterHook(builder.Configuration);
hook.AfterEndpointsAllocatedAsync(appModel, CancellationToken.None);

using var stream = File.OpenRead(volume.Source!);
Expand All @@ -303,7 +311,7 @@ public void WithPostgresProducesValidServersJsonFile()
// Make sure the first server is correct.
Assert.Equal(pg1.Resource.Name, servers.GetProperty("1").GetProperty("Name").GetString());
Assert.Equal("Aspire instances", servers.GetProperty("1").GetProperty("Group").GetString());
Assert.Equal("host.docker.internal", servers.GetProperty("1").GetProperty("Host").GetString());
Assert.Equal(expectedHost, servers.GetProperty("1").GetProperty("Host").GetString());
Assert.Equal(5001, servers.GetProperty("1").GetProperty("Port").GetInt32());
Assert.Equal("postgres", servers.GetProperty("1").GetProperty("Username").GetString());
Assert.Equal("prefer", servers.GetProperty("1").GetProperty("SSLMode").GetString());
Expand All @@ -313,7 +321,7 @@ public void WithPostgresProducesValidServersJsonFile()
// Make sure the second server is correct.
Assert.Equal(pg2.Resource.Name, servers.GetProperty("2").GetProperty("Name").GetString());
Assert.Equal("Aspire instances", servers.GetProperty("2").GetProperty("Group").GetString());
Assert.Equal("host.docker.internal", servers.GetProperty("2").GetProperty("Host").GetString());
Assert.Equal(expectedHost, servers.GetProperty("2").GetProperty("Host").GetString());
Assert.Equal(5002, servers.GetProperty("2").GetProperty("Port").GetInt32());
Assert.Equal("postgres", servers.GetProperty("2").GetProperty("Username").GetString());
Assert.Equal("prefer", servers.GetProperty("2").GetProperty("SSLMode").GetString());
Expand Down
Loading

0 comments on commit 692dc41

Please sign in to comment.