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
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,9 @@ public static IResourceBuilder<AzureEventHubsResource> RunAsEmulator(this IResou
var blobEndpoint = storage.GetEndpoint("blob");
var tableEndpoint = storage.GetEndpoint("table");

context.EnvironmentVariables.Add("ACCEPT_EULA", "Y");
context.EnvironmentVariables.Add("BLOB_SERVER", $"{blobEndpoint.Resource.Name}:{blobEndpoint.TargetPort}");
context.EnvironmentVariables.Add("METADATA_SERVER", $"{tableEndpoint.Resource.Name}:{tableEndpoint.TargetPort}");
context.EnvironmentVariables["ACCEPT_EULA"] = "Y";
context.EnvironmentVariables["BLOB_SERVER"] = $"{blobEndpoint.Resource.Name}:{blobEndpoint.TargetPort}";
context.EnvironmentVariables["METADATA_SERVER"] = $"{tableEndpoint.Resource.Name}:{tableEndpoint.TargetPort}";
}));

// RunAsEmulator() can be followed by custom model configuration so we need to delay the creation of the Config.json file
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -383,9 +383,9 @@ public static IResourceBuilder<AzureServiceBusResource> RunAsEmulator(this IReso
{
var sqlEndpoint = sqlServerResource.Resource.GetEndpoint("tcp");

context.EnvironmentVariables.Add("ACCEPT_EULA", "Y");
context.EnvironmentVariables.Add("SQL_SERVER", $"{sqlEndpoint.Resource.Name}:{sqlEndpoint.TargetPort}");
context.EnvironmentVariables.Add("MSSQL_SA_PASSWORD", passwordParameter);
context.EnvironmentVariables["ACCEPT_EULA"] = "Y";
context.EnvironmentVariables["SQL_SERVER"] = $"{sqlEndpoint.Resource.Name}:{sqlEndpoint.TargetPort}";
context.EnvironmentVariables["MSSQL_SA_PASSWORD"] = passwordParameter;
}));

var lifetime = ContainerLifetime.Session;
Expand Down
8 changes: 4 additions & 4 deletions src/Aspire.Hosting.Kafka/KafkaBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ static void ConfigureKafkaUIContainer(EnvironmentCallbackContext context, Endpoi
? ReferenceExpression.Create($"{endpoint.Resource.Name}:{endpoint.Property(EndpointProperty.TargetPort)}")
: ReferenceExpression.Create($"{endpoint.Property(EndpointProperty.HostAndPort)}");

context.EnvironmentVariables.Add($"KAFKA_CLUSTERS_{index}_NAME", endpoint.Resource.Name);
context.EnvironmentVariables.Add($"KAFKA_CLUSTERS_{index}_BOOTSTRAPSERVERS", bootstrapServers);
context.EnvironmentVariables[$"KAFKA_CLUSTERS_{index}_NAME"] = endpoint.Resource.Name;
context.EnvironmentVariables[$"KAFKA_CLUSTERS_{index}_BOOTSTRAPSERVERS"] = bootstrapServers;
}

}
Expand Down Expand Up @@ -203,9 +203,9 @@ private static void ConfigureKafkaContainer(EnvironmentCallbackContext context,
// See https://github.com/confluentinc/kafka-images/blob/master/local/include/etc/confluent/docker/configureDefaults for more details.

// Define the default listeners + an internal listener for the container to broker communication
context.EnvironmentVariables.Add($"KAFKA_LISTENERS", $"PLAINTEXT://localhost:29092,CONTROLLER://localhost:29093,PLAINTEXT_HOST://0.0.0.0:{KafkaBrokerPort},PLAINTEXT_INTERNAL://0.0.0.0:{KafkaInternalBrokerPort}");
context.EnvironmentVariables[$"KAFKA_LISTENERS"] = $"PLAINTEXT://localhost:29092,CONTROLLER://localhost:29093,PLAINTEXT_HOST://0.0.0.0:{KafkaBrokerPort},PLAINTEXT_INTERNAL://0.0.0.0:{KafkaInternalBrokerPort}";
// Defaults default listeners security protocol map + the internal listener to be PLAINTEXT
context.EnvironmentVariables.Add("KAFKA_LISTENER_SECURITY_PROTOCOL_MAP", "CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT,PLAINTEXT_INTERNAL:PLAINTEXT");
context.EnvironmentVariables["KAFKA_LISTENER_SECURITY_PROTOCOL_MAP"] = "CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT,PLAINTEXT_INTERNAL:PLAINTEXT";

// primaryEndpoint is the endpoint that is exposed to the host machine
var primaryEndpoint = resource.PrimaryEndpoint;
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Hosting.Milvus/MilvusBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,6 @@ private static void ConfigureAttuContainer(EnvironmentCallbackContext context, M
{
// Attu assumes Milvus is being accessed over a default Aspire container network and hardcodes the resource address
// This will need to be refactored once updated service discovery APIs are available
context.EnvironmentVariables.Add("MILVUS_URL", $"{resource.PrimaryEndpoint.Scheme}://{resource.Name}:{resource.PrimaryEndpoint.TargetPort}");
context.EnvironmentVariables["MILVUS_URL"] = $"{resource.PrimaryEndpoint.Scheme}://{resource.Name}:{resource.PrimaryEndpoint.TargetPort}";
}
}
10 changes: 5 additions & 5 deletions src/Aspire.Hosting.MongoDB/MongoDBBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -248,17 +248,17 @@ private static void ConfigureMongoExpressContainer(EnvironmentCallbackContext co
{
// Mongo Express assumes Mongo is being accessed over a default Aspire container network and hardcodes the resource address
// This will need to be refactored once updated service discovery APIs are available
context.EnvironmentVariables.Add("ME_CONFIG_MONGODB_SERVER", resource.Name);
context.EnvironmentVariables["ME_CONFIG_MONGODB_SERVER"] = resource.Name;
var targetPort = resource.PrimaryEndpoint.TargetPort;
if (targetPort is int targetPortValue)
{
context.EnvironmentVariables.Add("ME_CONFIG_MONGODB_PORT", targetPortValue.ToString(CultureInfo.InvariantCulture));
context.EnvironmentVariables["ME_CONFIG_MONGODB_PORT"] = targetPortValue.ToString(CultureInfo.InvariantCulture);
}
context.EnvironmentVariables.Add("ME_CONFIG_BASICAUTH", "false");
context.EnvironmentVariables["ME_CONFIG_BASICAUTH"] = "false";
if (resource.PasswordParameter is not null)
{
context.EnvironmentVariables.Add("ME_CONFIG_MONGODB_ADMINUSERNAME", resource.UserNameReference);
context.EnvironmentVariables.Add("ME_CONFIG_MONGODB_ADMINPASSWORD", resource.PasswordParameter);
context.EnvironmentVariables["ME_CONFIG_MONGODB_ADMINUSERNAME"] = resource.UserNameReference;
context.EnvironmentVariables["ME_CONFIG_MONGODB_ADMINPASSWORD"] = resource.PasswordParameter;
}
}
}
6 changes: 3 additions & 3 deletions src/Aspire.Hosting.MySql/MySqlBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,9 @@ public static IResourceBuilder<T> WithPhpMyAdmin<T>(this IResourceBuilder<T> bui
{
// PhpMyAdmin assumes MySql is being accessed over a default Aspire container network and hardcodes the resource address
// This will need to be refactored once updated service discovery APIs are available
context.EnvironmentVariables.Add("PMA_HOST", $"{endpoint.Resource.Name}:{endpoint.TargetPort}");
context.EnvironmentVariables.Add("PMA_USER", "root");
context.EnvironmentVariables.Add("PMA_PASSWORD", singleInstance.PasswordParameter);
context.EnvironmentVariables["PMA_HOST"] = $"{endpoint.Resource.Name}:{endpoint.TargetPort}";
context.EnvironmentVariables["PMA_USER"] = "root";
context.EnvironmentVariables["PMA_PASSWORD"] = singleInstance.PasswordParameter;
});
}
else
Expand Down
8 changes: 4 additions & 4 deletions src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -340,12 +340,12 @@ public static IResourceBuilder<PostgresServerResource> WithPgWeb(this IResourceB
private static void SetPgAdminEnvironmentVariables(EnvironmentCallbackContext context)
{
// Disables pgAdmin authentication.
context.EnvironmentVariables.Add("PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED", "False");
context.EnvironmentVariables.Add("PGADMIN_CONFIG_SERVER_MODE", "False");
context.EnvironmentVariables["PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED"] = "False";
context.EnvironmentVariables["PGADMIN_CONFIG_SERVER_MODE"] = "False";

// You need to define the PGADMIN_DEFAULT_EMAIL and PGADMIN_DEFAULT_PASSWORD or PGADMIN_DEFAULT_PASSWORD_FILE environment variables.
context.EnvironmentVariables.Add("PGADMIN_DEFAULT_EMAIL", "[email protected]");
context.EnvironmentVariables.Add("PGADMIN_DEFAULT_PASSWORD", "admin");
context.EnvironmentVariables["PGADMIN_DEFAULT_EMAIL"] = "[email protected]";
context.EnvironmentVariables["PGADMIN_DEFAULT_PASSWORD"] = "admin";

// When running in the context of Codespaces we need to set some additional environment
// variables so that PGAdmin will trust the forwarded headers that Codespaces port
Expand Down
8 changes: 4 additions & 4 deletions src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,12 +238,12 @@ public static IResourceBuilder<RedisResource> WithRedisInsight(this IResourceBui
foreach (var redisInstance in redisInstances)
{
// RedisInsight assumes Redis is being accessed over a default Aspire container network and hardcodes the resource address
context.EnvironmentVariables.Add($"RI_REDIS_HOST{counter}", redisInstance.Name);
context.EnvironmentVariables.Add($"RI_REDIS_PORT{counter}", redisInstance.PrimaryEndpoint.TargetPort!.Value);
context.EnvironmentVariables.Add($"RI_REDIS_ALIAS{counter}", redisInstance.Name);
context.EnvironmentVariables[$"RI_REDIS_HOST{counter}"] = redisInstance.Name;
context.EnvironmentVariables[$"RI_REDIS_PORT{counter}"] = redisInstance.PrimaryEndpoint.TargetPort!.Value;
context.EnvironmentVariables[$"RI_REDIS_ALIAS{counter}"] = redisInstance.Name;
if (redisInstance.PasswordParameter is not null)
{
context.EnvironmentVariables.Add($"RI_REDIS_PASSWORD{counter}", redisInstance.PasswordParameter);
context.EnvironmentVariables[$"RI_REDIS_PASSWORD{counter}"] = redisInstance.PasswordParameter;
}

counter++;
Expand Down
52 changes: 52 additions & 0 deletions tests/Aspire.Hosting.Kafka.Tests/AddKafkaTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Net.Sockets;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Eventing;
using Aspire.Hosting.Utils;
using Microsoft.Extensions.DependencyInjection;

Expand Down Expand Up @@ -176,4 +177,55 @@ public void WithKafkaUIAddsAnUniqueContainerSetsItsNameAndInvokesConfigurationCa
Assert.Equal(8080, kafkaUiEndpoint.TargetPort);
Assert.Equal(port, kafkaUiEndpoint.Port);
}

[Fact]
public async Task KafkaEnvironmentCallbackIsIdempotent()
{
using var appBuilder = TestDistributedApplicationBuilder.Create();

var kafka = appBuilder.AddKafka("kafka")
.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 27017));

// Call GetEnvironmentVariableValuesAsync multiple times to ensure callbacks are idempotent
var config1 = await kafka.Resource.GetEnvironmentVariableValuesAsync();
var config2 = await kafka.Resource.GetEnvironmentVariableValuesAsync();

// Both calls should succeed and return the same values
Assert.Equal(config1.Count, config2.Count);
Assert.Contains(config1, kvp => kvp.Key == "KAFKA_LISTENERS");
Assert.Contains(config2, kvp => kvp.Key == "KAFKA_LISTENERS");
Assert.Equal(
config1.First(kvp => kvp.Key == "KAFKA_LISTENERS").Value,
config2.First(kvp => kvp.Key == "KAFKA_LISTENERS").Value);
}

[Fact]
public async Task KafkaUIEnvironmentCallbackIsIdempotent()
{
using var appBuilder = TestDistributedApplicationBuilder.Create();

var kafka = appBuilder.AddKafka("kafka1")
.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 27017))
.WithKafkaUI();

using var app = appBuilder.Build();
var appModel = app.Services.GetRequiredService<DistributedApplicationModel>();
var kafkaUiResource = Assert.Single(appModel.Resources.OfType<KafkaUIContainerResource>());

// Trigger the BeforeResourceStartedEvent to add environment callbacks
await appBuilder.Eventing.PublishAsync(
new BeforeResourceStartedEvent(kafkaUiResource, app.Services),
EventDispatchBehavior.BlockingSequential);

// Call GetEnvironmentVariableValuesAsync multiple times to ensure callbacks are idempotent
var config1 = await kafkaUiResource.GetEnvironmentVariableValuesAsync();
var config2 = await kafkaUiResource.GetEnvironmentVariableValuesAsync();

// Both calls should succeed and return the same values
Assert.Equal(config1.Count, config2.Count);
Assert.Contains(config1, kvp => kvp.Key == "KAFKA_CLUSTERS_0_NAME");
Assert.Contains(config2, kvp => kvp.Key == "KAFKA_CLUSTERS_0_NAME");
Assert.Equal("kafka1", config1.First(kvp => kvp.Key == "KAFKA_CLUSTERS_0_NAME").Value);
Assert.Equal("kafka1", config2.First(kvp => kvp.Key == "KAFKA_CLUSTERS_0_NAME").Value);
}
}
26 changes: 26 additions & 0 deletions tests/Aspire.Hosting.MongoDB.Tests/AddMongoDBTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -286,4 +286,30 @@ public void CanAddDatabasesWithTheSameNameOnMultipleServers()
Assert.Equal("mongodb://admin:{mongo1-password.value}@{mongo1.bindings.tcp.host}:{mongo1.bindings.tcp.port}/imports?authSource=admin&authMechanism=SCRAM-SHA-256", db1.Resource.ConnectionStringExpression.ValueExpression);
Assert.Equal("mongodb://admin:{mongo2-password.value}@{mongo2.bindings.tcp.host}:{mongo2.bindings.tcp.port}/imports?authSource=admin&authMechanism=SCRAM-SHA-256", db2.Resource.ConnectionStringExpression.ValueExpression);
}

[Fact]
public async Task MongoExpressEnvironmentCallbackIsIdempotent()
{
using var appBuilder = TestDistributedApplicationBuilder.Create();

var mongo = appBuilder.AddMongoDB("mongo")
.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 27017))
.WithMongoExpress();

using var app = appBuilder.Build();
var appModel = app.Services.GetRequiredService<DistributedApplicationModel>();
var mongoExpressResource = Assert.Single(appModel.Resources.OfType<MongoExpressContainerResource>());

// Call GetEnvironmentVariableValuesAsync multiple times to ensure callbacks are idempotent
var config1 = await mongoExpressResource.GetEnvironmentVariableValuesAsync();
var config2 = await mongoExpressResource.GetEnvironmentVariableValuesAsync();

// Both calls should succeed and return the same values
Assert.Equal(config1.Count, config2.Count);
Assert.Contains(config1, kvp => kvp.Key == "ME_CONFIG_MONGODB_SERVER");
Assert.Contains(config2, kvp => kvp.Key == "ME_CONFIG_MONGODB_SERVER");
Assert.Equal(
config1.First(kvp => kvp.Key == "ME_CONFIG_MONGODB_SERVER").Value,
config2.First(kvp => kvp.Key == "ME_CONFIG_MONGODB_SERVER").Value);
}
}
32 changes: 32 additions & 0 deletions tests/Aspire.Hosting.MySql.Tests/AddMySqlTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Net.Sockets;
using System.Text.RegularExpressions;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Eventing;
using Aspire.Hosting.Tests.Utils;
using Aspire.Hosting.Utils;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -365,4 +366,35 @@ public async Task VerifyMySqlServerResourceWithPassword()
var connectionString = await connectionStringResource.ConnectionStringExpression.GetValueAsync(default);
Assert.Equal("Server=localhost;Port=2000;User ID=root;Password=p@ssw0rd1", connectionString);
}

[Fact]
public async Task PhpMyAdminEnvironmentCallbackIsIdempotent()
{
using var appBuilder = TestDistributedApplicationBuilder.Create();

var mysql = appBuilder.AddMySql("mysql")
.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 3306))
.WithPhpMyAdmin();

using var app = appBuilder.Build();
var appModel = app.Services.GetRequiredService<DistributedApplicationModel>();
var phpMyAdminResource = Assert.Single(appModel.Resources.OfType<PhpMyAdminContainerResource>());

// Trigger the BeforeResourceStartedEvent to add environment callbacks
await appBuilder.Eventing.PublishAsync(
new BeforeResourceStartedEvent(phpMyAdminResource, app.Services),
EventDispatchBehavior.BlockingSequential);

// Call GetEnvironmentVariableValuesAsync multiple times to ensure callbacks are idempotent
var config1 = await phpMyAdminResource.GetEnvironmentVariableValuesAsync();
var config2 = await phpMyAdminResource.GetEnvironmentVariableValuesAsync();

// Both calls should succeed and return the same values
Assert.Equal(config1.Count, config2.Count);
Assert.Contains(config1, kvp => kvp.Key == "PMA_HOST");
Assert.Contains(config2, kvp => kvp.Key == "PMA_HOST");
Assert.Equal(
config1.First(kvp => kvp.Key == "PMA_HOST").Value,
config2.First(kvp => kvp.Key == "PMA_HOST").Value);
}
}
22 changes: 22 additions & 0 deletions tests/Aspire.Hosting.PostgreSQL.Tests/AddPostgresTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -684,4 +684,26 @@ public async Task VerifyPostgresServerResourceWithUserName()
Assert.Equal($"Host=localhost;Port=2000;Username=user1;Password={postgres.Resource.PasswordParameter.Value}", connectionString);
#pragma warning restore CS0618 // Type or member is obsolete
}

[Fact]
public async Task PostgresEnvironmentCallbackIsIdempotent()
{
using var appBuilder = TestDistributedApplicationBuilder.Create();

var postgres = appBuilder.AddPostgres("postgres")
.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 5432));

// Call GetEnvironmentVariableValuesAsync multiple times to ensure callbacks are idempotent
var config1 = await postgres.Resource.GetEnvironmentVariableValuesAsync();
var config2 = await postgres.Resource.GetEnvironmentVariableValuesAsync();

// Both calls should succeed and return the same values
Assert.Equal(config1.Count, config2.Count);
// Verify that environment variables are set consistently across multiple calls
Assert.All(config1, kvp =>
{
Assert.True(config2.ContainsKey(kvp.Key), $"Key {kvp.Key} should exist in second call");
Assert.Equal(kvp.Value, config2[kvp.Key]);
});
}
}
26 changes: 26 additions & 0 deletions tests/Aspire.Hosting.Redis.Tests/AddRedisTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -701,4 +701,30 @@ public async Task AddRedisContainerWithPasswordAnnotationMetadata()
Assert.Equal("{myRedis.bindings.tcp.host}:{myRedis.bindings.tcp.port},password={pass.value}", connectionStringResource.ConnectionStringExpression.ValueExpression);
Assert.StartsWith($"localhost:5001,password={password}", connectionString);
}

[Fact]
public async Task RedisInsightEnvironmentCallbackIsIdempotent()
{
using var appBuilder = TestDistributedApplicationBuilder.Create();

var redis = appBuilder.AddRedis("redis")
.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 6379))
.WithRedisInsight();

using var app = appBuilder.Build();
var appModel = app.Services.GetRequiredService<DistributedApplicationModel>();
var redisInsightResource = Assert.Single(appModel.Resources.OfType<RedisInsightResource>());

// Call GetEnvironmentVariableValuesAsync multiple times to ensure callbacks are idempotent
var config1 = await redisInsightResource.GetEnvironmentVariableValuesAsync();
var config2 = await redisInsightResource.GetEnvironmentVariableValuesAsync();

// Both calls should succeed and return the same values
Assert.Equal(config1.Count, config2.Count);
Assert.Contains(config1, kvp => kvp.Key == "RI_REDIS_HOST1");
Assert.Contains(config2, kvp => kvp.Key == "RI_REDIS_HOST1");
Assert.Equal(
config1.First(kvp => kvp.Key == "RI_REDIS_HOST1").Value,
config2.First(kvp => kvp.Key == "RI_REDIS_HOST1").Value);
}
}