From 4e998d2d0aa56368f574b2902a0f7d0f91ee9bf9 Mon Sep 17 00:00:00 2001 From: Alireza Baloochi Date: Mon, 31 Mar 2025 14:23:20 +0330 Subject: [PATCH 01/17] Provide port and password for AzureSql RunAsContainer --- .../SqlServerEndToEnd.AppHost/Program.cs | 5 +++- .../AzureSqlExtensions.cs | 10 ++++++-- .../RunAsContainerOptions.cs | 24 +++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 src/Aspire.Hosting.Azure.Sql/RunAsContainerOptions.cs diff --git a/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs b/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs index e120df70897..30b48abe05c 100644 --- a/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs +++ b/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs @@ -4,7 +4,10 @@ var builder = DistributedApplication.CreateBuilder(args); var sql1 = builder.AddAzureSqlServer("sql1") - .RunAsContainer(); + .RunAsContainer(configureOptions: c => + { + c.Port = 1433; + }); var db1 = sql1.AddDatabase("db1"); diff --git a/src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs b/src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs index 8c6694be2d6..acb84559656 100644 --- a/src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs +++ b/src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs @@ -3,6 +3,7 @@ using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Azure; +using Aspire.Hosting.Azure.Sql; using Azure.Provisioning; using Azure.Provisioning.Expressions; using Azure.Provisioning.Primitives; @@ -132,6 +133,7 @@ public static IResourceBuilder AddDatabase(this IResou /// /// The builder for the Azure SQL resource. /// Callback that exposes underlying container to allow for customization. + /// Callback to configure underlying container options including password and port. /// A reference to the builder. /// /// The following example creates an Azure SQL Database (server) resource that runs locally in a @@ -148,7 +150,7 @@ public static IResourceBuilder AddDatabase(this IResou /// builder.Build().Run(); /// /// - public static IResourceBuilder RunAsContainer(this IResourceBuilder builder, Action>? configureContainer = null) + public static IResourceBuilder RunAsContainer(this IResourceBuilder builder, Action>? configureContainer = null, Action? configureOptions = null) { ArgumentNullException.ThrowIfNull(builder); @@ -165,7 +167,11 @@ public static IResourceBuilder RunAsContainer(this IReso RemoveAzureResources(builder.ApplicationBuilder, azureResource, azureDatabases); - var sqlContainer = builder.ApplicationBuilder.AddSqlServer(azureResource.Name); + var runAsContainerOptions = new RunAsContainerOptions(); + + configureOptions?.Invoke(runAsContainerOptions); + + var sqlContainer = builder.ApplicationBuilder.AddSqlServer(azureResource.Name, password: runAsContainerOptions.Password, runAsContainerOptions.Port); azureResource.SetInnerResource(sqlContainer.Resource); diff --git a/src/Aspire.Hosting.Azure.Sql/RunAsContainerOptions.cs b/src/Aspire.Hosting.Azure.Sql/RunAsContainerOptions.cs new file mode 100644 index 00000000000..15343096b93 --- /dev/null +++ b/src/Aspire.Hosting.Azure.Sql/RunAsContainerOptions.cs @@ -0,0 +1,24 @@ +// 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.ApplicationModel; + +namespace Aspire.Hosting.Azure.Sql; + +/// +/// Represents options for running a SQL Server as a container. +/// This class allows configuration of the SQL Server container, +/// including the port to expose and the password for the SQL Server instance. +/// +public class RunAsContainerOptions +{ + /// + /// Gets or Sets the port for the SQL Server. + /// + public int? Port { get; set; } + + /// + /// Gets or Sets the password for the SQL Server. + /// + public IResourceBuilder? Password { get; set; } +} From 77d57bfb8f403207e277675036d8a62953bfa455 Mon Sep 17 00:00:00 2001 From: Alireza Baloochi Date: Mon, 31 Mar 2025 17:59:35 +0330 Subject: [PATCH 02/17] Add tests --- .../AzureSqlExtensionsTests.cs | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureSqlExtensionsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureSqlExtensionsTests.cs index 0b00ea47faf..022a89abbae 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureSqlExtensionsTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureSqlExtensionsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Net.Sockets; using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Utils; using Xunit; @@ -189,6 +190,63 @@ public async Task AddAzureSqlServerRunAsContainerProducesCorrectConnectionString Assert.EndsWith(";TrustServerCertificate=true;Database=db2Name", db2ConnectionString); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task AddAzureSqlServerRunAsContainerProducesCorrectPasswordAndPort(bool addDbBeforeRunAsContainer) + { + using var builder = TestDistributedApplicationBuilder.Create(); + + var sql = builder.AddAzureSqlServer("sql"); + var pass = builder.AddParameter("pass", "p@ssw0rd1"); + IResourceBuilder db1 = null!; + IResourceBuilder db2 = null!; + if (addDbBeforeRunAsContainer) + { + db1 = sql.AddDatabase("db1"); + db2 = sql.AddDatabase("db2", "db2Name"); + + } + + IResourceBuilder? innerSql = null; + sql.RunAsContainer(configureOptions: c => + { + c.Port = 12455; + c.Password = pass; + }, configureContainer: c => + { + innerSql = c; + c.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 12455)); + }); + + Assert.NotNull(innerSql); + + if (!addDbBeforeRunAsContainer) + { + db1 = sql.AddDatabase("db1"); + db2 = sql.AddDatabase("db2", "db2Name"); + } + + var endpoint = Assert.Single(innerSql.Resource.Annotations.OfType()); + Assert.Equal(1433, endpoint.TargetPort); + Assert.False(endpoint.IsExternal); + Assert.Equal("tcp", endpoint.Name); + Assert.Equal(12455, endpoint.Port); + Assert.Equal(ProtocolType.Tcp, endpoint.Protocol); + Assert.Equal("tcp", endpoint.Transport); + Assert.Equal("tcp", endpoint.UriScheme); + + Assert.True(sql.Resource.IsContainer(), "The resource should now be a container resource."); + var serverConnectionString = await sql.Resource.ConnectionStringExpression.GetValueAsync(CancellationToken.None); + Assert.Equal("Server=127.0.0.1,12455;User ID=sa;Password=p@ssw0rd1;TrustServerCertificate=true", serverConnectionString); + + var db1ConnectionString = await db1.Resource.ConnectionStringExpression.GetValueAsync(CancellationToken.None); + Assert.StartsWith("Server=127.0.0.1,12455;User ID=sa;Password=p@ssw0rd1;TrustServerCertificate=true;Database=db1", db1ConnectionString); + + var db2ConnectionString = await db2.Resource.ConnectionStringExpression.GetValueAsync(CancellationToken.None); + Assert.StartsWith("Server=127.0.0.1,12455;User ID=sa;Password=p@ssw0rd1;TrustServerCertificate=true;Database=db2Name", db2ConnectionString); + } + [Theory] [InlineData(true, true)] [InlineData(true, false)] From a26d3f95a5793a5c839353a0fdaa6cbd31ad4ba1 Mon Sep 17 00:00:00 2001 From: Alireza Baloochi Date: Mon, 31 Mar 2025 22:17:10 +0330 Subject: [PATCH 03/17] Revert playground app changes --- .../SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs b/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs index 30b48abe05c..e120df70897 100644 --- a/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs +++ b/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs @@ -4,10 +4,7 @@ var builder = DistributedApplication.CreateBuilder(args); var sql1 = builder.AddAzureSqlServer("sql1") - .RunAsContainer(configureOptions: c => - { - c.Port = 1433; - }); + .RunAsContainer(); var db1 = sql1.AddDatabase("db1"); From aefd4e8de7df9111a5e03ee399232225c69e5277 Mon Sep 17 00:00:00 2001 From: Alireza Baloochi Date: Mon, 31 Mar 2025 19:02:05 +0000 Subject: [PATCH 04/17] suppress api-diff --- .../CompatibilitySuppressions.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/Aspire.Hosting.Azure.Sql/CompatibilitySuppressions.xml diff --git a/src/Aspire.Hosting.Azure.Sql/CompatibilitySuppressions.xml b/src/Aspire.Hosting.Azure.Sql/CompatibilitySuppressions.xml new file mode 100644 index 00000000000..387715e0963 --- /dev/null +++ b/src/Aspire.Hosting.Azure.Sql/CompatibilitySuppressions.xml @@ -0,0 +1,11 @@ + + + + + CP0002 + M:Aspire.Hosting.AzureSqlExtensions.RunAsContainer(Aspire.Hosting.ApplicationModel.IResourceBuilder{Aspire.Hosting.Azure.AzureSqlServerResource},System.Action{Aspire.Hosting.ApplicationModel.IResourceBuilder{Aspire.Hosting.ApplicationModel.SqlServerServerResource}}) + lib/net8.0/Aspire.Hosting.Azure.Sql.dll + lib/net8.0/Aspire.Hosting.Azure.Sql.dll + true + + \ No newline at end of file From 96849d2fc091d7e8ccd3e2b6d69392caf9e38747 Mon Sep 17 00:00:00 2001 From: Alireza Baloochi Date: Wed, 2 Apr 2025 02:07:02 +0330 Subject: [PATCH 05/17] Add new overload --- .../AzureSqlExtensions.cs | 28 +++++++++++++++++++ .../CompatibilitySuppressions.xml | 11 -------- 2 files changed, 28 insertions(+), 11 deletions(-) delete mode 100644 src/Aspire.Hosting.Azure.Sql/CompatibilitySuppressions.xml diff --git a/src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs b/src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs index acb84559656..ba99f959b4e 100644 --- a/src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs +++ b/src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs @@ -128,6 +128,34 @@ public static IResourceBuilder AddDatabase(this IResou } } + /// + /// Configures an Azure SQL Database (server) resource to run locally in a container. + /// + /// The builder for the Azure SQL resource. + /// Callback that exposes underlying container to allow for customization. + /// A reference to the builder. + /// + /// The following example creates an Azure SQL Database (server) resource that runs locally in a + /// SQL Server container and referencing that resource in a .NET project. + /// + /// var builder = DistributedApplication.CreateBuilder(args); + /// + /// var data = builder.AddAzureSqlServer("data") + /// .RunAsContainer(); + /// + /// builder.AddProject<Projects.ProductService>() + /// .WithReference(data); + /// + /// builder.Build().Run(); + /// + /// + public static IResourceBuilder RunAsContainer(this IResourceBuilder builder, Action>? configureContainer = null) + { + ArgumentNullException.ThrowIfNull(builder); + + return builder.RunAsContainer(configureContainer, configureOptions: null); + } + /// /// Configures an Azure SQL Database (server) resource to run locally in a container. /// diff --git a/src/Aspire.Hosting.Azure.Sql/CompatibilitySuppressions.xml b/src/Aspire.Hosting.Azure.Sql/CompatibilitySuppressions.xml deleted file mode 100644 index 387715e0963..00000000000 --- a/src/Aspire.Hosting.Azure.Sql/CompatibilitySuppressions.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - CP0002 - M:Aspire.Hosting.AzureSqlExtensions.RunAsContainer(Aspire.Hosting.ApplicationModel.IResourceBuilder{Aspire.Hosting.Azure.AzureSqlServerResource},System.Action{Aspire.Hosting.ApplicationModel.IResourceBuilder{Aspire.Hosting.ApplicationModel.SqlServerServerResource}}) - lib/net8.0/Aspire.Hosting.Azure.Sql.dll - lib/net8.0/Aspire.Hosting.Azure.Sql.dll - true - - \ No newline at end of file From f462b63d32c538a805c540f054e2b7e7f32f2414 Mon Sep 17 00:00:00 2001 From: Alireza Baloochi Date: Wed, 2 Apr 2025 02:34:11 +0330 Subject: [PATCH 06/17] Fix build --- src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs b/src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs index ba99f959b4e..f420c1b8318 100644 --- a/src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs +++ b/src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs @@ -149,7 +149,7 @@ public static IResourceBuilder AddDatabase(this IResou /// builder.Build().Run(); /// /// - public static IResourceBuilder RunAsContainer(this IResourceBuilder builder, Action>? configureContainer = null) + public static IResourceBuilder RunAsContainer(this IResourceBuilder builder, Action> configureContainer) { ArgumentNullException.ThrowIfNull(builder); From 4aef0cd29b3f39ec796b48db42a10aa888c28daf Mon Sep 17 00:00:00 2001 From: Alireza Baloochi Date: Wed, 2 Apr 2025 09:56:12 +0330 Subject: [PATCH 07/17] Update src/Aspire.Hosting.Azure.Sql/RunAsContainerOptions.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Ros --- src/Aspire.Hosting.Azure.Sql/RunAsContainerOptions.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Aspire.Hosting.Azure.Sql/RunAsContainerOptions.cs b/src/Aspire.Hosting.Azure.Sql/RunAsContainerOptions.cs index 15343096b93..a1002d09a55 100644 --- a/src/Aspire.Hosting.Azure.Sql/RunAsContainerOptions.cs +++ b/src/Aspire.Hosting.Azure.Sql/RunAsContainerOptions.cs @@ -5,12 +5,10 @@ namespace Aspire.Hosting.Azure.Sql; -/// -/// Represents options for running a SQL Server as a container. -/// This class allows configuration of the SQL Server container, -/// including the port to expose and the password for the SQL Server instance. -/// -public class RunAsContainerOptions +/// +/// Options for configuring the behavior of . +/// +public sealed class RunAsContainerOptions { /// /// Gets or Sets the port for the SQL Server. From 0390ba0a9b702d4b80eaa6ea64a5c1aff42933a9 Mon Sep 17 00:00:00 2001 From: Alireza Baloochi Date: Wed, 2 Apr 2025 20:25:59 +0330 Subject: [PATCH 08/17] Add WithHostPort and WithPassword to SqlServer --- .../SqlServerBuilderExtensions.cs | 34 +++++++++++++++++++ .../SqlServerServerResource.cs | 15 +++++++- .../AzureSqlExtensionsTests.cs | 10 +++--- 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs b/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs index 977ea4c7cd3..2ff4210c0ed 100644 --- a/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs +++ b/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs @@ -200,6 +200,40 @@ public static IResourceBuilder WithCreationScript(thi return builder; } + /// + /// TODO + /// + /// + /// + /// + public static IResourceBuilder WithPassword(this IResourceBuilder builder, IResourceBuilder password) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(password); + + var sqlserver = builder.Resource.WithPassword(password.Resource); + return builder.WithEnvironment(context => + { + context.EnvironmentVariables["MSSQL_SA_PASSWORD"] = sqlserver.PasswordParameter; + }); + } + + /// + /// TODO + /// + /// + /// + /// + public static IResourceBuilder WithHostPort(this IResourceBuilder builder, int port) + { + ArgumentNullException.ThrowIfNull(builder); + return builder.WithEndpoint(SqlServerServerResource.PrimaryEndpointName, endpoint => + { + endpoint.Port = port; + }); + + } + private static async Task CreateDatabaseAsync(SqlConnection sqlConnection, SqlServerDatabaseResource sqlDatabase, IServiceProvider serviceProvider, CancellationToken ct) { try diff --git a/src/Aspire.Hosting.SqlServer/SqlServerServerResource.cs b/src/Aspire.Hosting.SqlServer/SqlServerServerResource.cs index addfa0b027a..b9a90993afd 100644 --- a/src/Aspire.Hosting.SqlServer/SqlServerServerResource.cs +++ b/src/Aspire.Hosting.SqlServer/SqlServerServerResource.cs @@ -31,7 +31,7 @@ public SqlServerServerResource(string name, ParameterResource password) : base(n /// /// Gets the parameter that contains the SQL Server password. /// - public ParameterResource PasswordParameter { get; } + public ParameterResource PasswordParameter { get; private set; } private ReferenceExpression ConnectionString => ReferenceExpression.Create( @@ -76,6 +76,19 @@ public ReferenceExpression ConnectionStringExpression /// public IReadOnlyDictionary Databases => _databases; + /// + /// TODO + /// + /// + /// + public SqlServerServerResource WithPassword(ParameterResource password) + { + ArgumentNullException.ThrowIfNull(password); + + PasswordParameter = password; + return this; + } + internal void AddDatabase(SqlServerDatabaseResource database) { _databases.TryAdd(database.Name, database.DatabaseName); diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureSqlExtensionsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureSqlExtensionsTests.cs index 022a89abbae..2d365a773b2 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureSqlExtensionsTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureSqlExtensionsTests.cs @@ -209,14 +209,12 @@ public async Task AddAzureSqlServerRunAsContainerProducesCorrectPasswordAndPort( } IResourceBuilder? innerSql = null; - sql.RunAsContainer(configureOptions: c => + sql.RunAsContainer(configureContainer: c => { - c.Port = 12455; - c.Password = pass; - }, configureContainer: c => - { - innerSql = c; c.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 12455)); + c.WithHostPort(12455) + .WithPassword(pass); + innerSql = c; }); Assert.NotNull(innerSql); From cf5fe4737a9707d1ef3f62bed12e858150d60e99 Mon Sep 17 00:00:00 2001 From: Alireza Baloochi Date: Wed, 2 Apr 2025 20:27:47 +0330 Subject: [PATCH 09/17] Revert AzureSqlExtensions --- .../AzureSqlExtensions.cs | 38 +------------------ .../RunAsContainerOptions.cs | 22 ----------- 2 files changed, 2 insertions(+), 58 deletions(-) delete mode 100644 src/Aspire.Hosting.Azure.Sql/RunAsContainerOptions.cs diff --git a/src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs b/src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs index f420c1b8318..8c6694be2d6 100644 --- a/src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs +++ b/src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs @@ -3,7 +3,6 @@ using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Azure; -using Aspire.Hosting.Azure.Sql; using Azure.Provisioning; using Azure.Provisioning.Expressions; using Azure.Provisioning.Primitives; @@ -149,36 +148,7 @@ public static IResourceBuilder AddDatabase(this IResou /// builder.Build().Run(); /// /// - public static IResourceBuilder RunAsContainer(this IResourceBuilder builder, Action> configureContainer) - { - ArgumentNullException.ThrowIfNull(builder); - - return builder.RunAsContainer(configureContainer, configureOptions: null); - } - - /// - /// Configures an Azure SQL Database (server) resource to run locally in a container. - /// - /// The builder for the Azure SQL resource. - /// Callback that exposes underlying container to allow for customization. - /// Callback to configure underlying container options including password and port. - /// A reference to the builder. - /// - /// The following example creates an Azure SQL Database (server) resource that runs locally in a - /// SQL Server container and referencing that resource in a .NET project. - /// - /// var builder = DistributedApplication.CreateBuilder(args); - /// - /// var data = builder.AddAzureSqlServer("data") - /// .RunAsContainer(); - /// - /// builder.AddProject<Projects.ProductService>() - /// .WithReference(data); - /// - /// builder.Build().Run(); - /// - /// - public static IResourceBuilder RunAsContainer(this IResourceBuilder builder, Action>? configureContainer = null, Action? configureOptions = null) + public static IResourceBuilder RunAsContainer(this IResourceBuilder builder, Action>? configureContainer = null) { ArgumentNullException.ThrowIfNull(builder); @@ -195,11 +165,7 @@ public static IResourceBuilder RunAsContainer(this IReso RemoveAzureResources(builder.ApplicationBuilder, azureResource, azureDatabases); - var runAsContainerOptions = new RunAsContainerOptions(); - - configureOptions?.Invoke(runAsContainerOptions); - - var sqlContainer = builder.ApplicationBuilder.AddSqlServer(azureResource.Name, password: runAsContainerOptions.Password, runAsContainerOptions.Port); + var sqlContainer = builder.ApplicationBuilder.AddSqlServer(azureResource.Name); azureResource.SetInnerResource(sqlContainer.Resource); diff --git a/src/Aspire.Hosting.Azure.Sql/RunAsContainerOptions.cs b/src/Aspire.Hosting.Azure.Sql/RunAsContainerOptions.cs deleted file mode 100644 index a1002d09a55..00000000000 --- a/src/Aspire.Hosting.Azure.Sql/RunAsContainerOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -// 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.ApplicationModel; - -namespace Aspire.Hosting.Azure.Sql; - -/// -/// Options for configuring the behavior of . -/// -public sealed class RunAsContainerOptions -{ - /// - /// Gets or Sets the port for the SQL Server. - /// - public int? Port { get; set; } - - /// - /// Gets or Sets the password for the SQL Server. - /// - public IResourceBuilder? Password { get; set; } -} From 928604c65dbecb64a4f6d9dd3a1f4dcf25122064 Mon Sep 17 00:00:00 2001 From: Alireza Baloochi Date: Wed, 2 Apr 2025 23:20:21 +0330 Subject: [PATCH 10/17] Make SetPassword internal --- .../SqlServerBuilderExtensions.cs | 4 ++-- src/Aspire.Hosting.SqlServer/SqlServerServerResource.cs | 8 +------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs b/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs index 2ff4210c0ed..53a24d29041 100644 --- a/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs +++ b/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs @@ -211,10 +211,10 @@ public static IResourceBuilder WithPassword(this IResou ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(password); - var sqlserver = builder.Resource.WithPassword(password.Resource); + builder.Resource.SetPassword(password.Resource); return builder.WithEnvironment(context => { - context.EnvironmentVariables["MSSQL_SA_PASSWORD"] = sqlserver.PasswordParameter; + context.EnvironmentVariables["MSSQL_SA_PASSWORD"] = builder.Resource.PasswordParameter; }); } diff --git a/src/Aspire.Hosting.SqlServer/SqlServerServerResource.cs b/src/Aspire.Hosting.SqlServer/SqlServerServerResource.cs index b9a90993afd..8cf381b2cbc 100644 --- a/src/Aspire.Hosting.SqlServer/SqlServerServerResource.cs +++ b/src/Aspire.Hosting.SqlServer/SqlServerServerResource.cs @@ -76,17 +76,11 @@ public ReferenceExpression ConnectionStringExpression /// public IReadOnlyDictionary Databases => _databases; - /// - /// TODO - /// - /// - /// - public SqlServerServerResource WithPassword(ParameterResource password) + internal void SetPassword(ParameterResource password) { ArgumentNullException.ThrowIfNull(password); PasswordParameter = password; - return this; } internal void AddDatabase(SqlServerDatabaseResource database) From 9f2ba9b2cf13c1f81a7c68f23427ee0e87ba7069 Mon Sep 17 00:00:00 2001 From: Alireza Baloochi Date: Wed, 2 Apr 2025 23:52:20 +0330 Subject: [PATCH 11/17] Add WithHost,WithPassword and WithUserName for PostgresServerResource --- .config/CredScanSuppressions.json | 4 ++ .../PostgresBuilderExtensions.cs | 52 +++++++++++++++++ .../PostgresServerResource.cs | 14 +++++ .../AzurePostgresExtensionsTests.cs | 58 +++++++++++++++++++ 4 files changed, 128 insertions(+) diff --git a/.config/CredScanSuppressions.json b/.config/CredScanSuppressions.json index 0b52abb9deb..b9fe2831201 100644 --- a/.config/CredScanSuppressions.json +++ b/.config/CredScanSuppressions.json @@ -36,6 +36,10 @@ { "file": "\\tests\\Shared\\TestCertificates\\testCert.pfx", "_justification": "Legitimate UT certificate file with private key, from dotnet/aspnetcore" + }, + { + "placeholder": "user1", + "_justification": "This is a fake username, used in tests." } ] } diff --git a/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs b/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs index fc4b463bd24..0c384ed7f0d 100644 --- a/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs +++ b/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs @@ -424,6 +424,58 @@ public static IResourceBuilder WithCreationScript(this return builder; } + /// + /// TODO + /// + /// + /// + /// + public static IResourceBuilder WithPassword(this IResourceBuilder builder, IResourceBuilder password) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(password); + + builder.Resource.SetPassword(password.Resource); + return builder.WithEnvironment(context => + { + context.EnvironmentVariables[PasswordEnvVarName] = builder.Resource.PasswordParameter; + }); + } + + /// + /// TODO + /// + /// + /// + /// + public static IResourceBuilder WithUserName(this IResourceBuilder builder, IResourceBuilder userName) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(userName); + + builder.Resource.SetUserName(userName.Resource); + return builder.WithEnvironment(context => + { + context.EnvironmentVariables[UserEnvVarName] = builder.Resource.UserNameReference; + }); + } + + /// + /// TODO + /// + /// + /// + /// + public static IResourceBuilder WithHostPort(this IResourceBuilder builder, int port) + { + ArgumentNullException.ThrowIfNull(builder); + return builder.WithEndpoint(PostgresServerResource.PrimaryEndpointName, endpoint => + { + endpoint.Port = port; + }); + + } + private static IEnumerable WritePgWebBookmarks(IEnumerable postgresInstances) { var bookmarkFiles = new List(); diff --git a/src/Aspire.Hosting.PostgreSQL/PostgresServerResource.cs b/src/Aspire.Hosting.PostgreSQL/PostgresServerResource.cs index fb34562a4e2..70cd01f9874 100644 --- a/src/Aspire.Hosting.PostgreSQL/PostgresServerResource.cs +++ b/src/Aspire.Hosting.PostgreSQL/PostgresServerResource.cs @@ -88,6 +88,20 @@ public ReferenceExpression ConnectionStringExpression /// public IReadOnlyDictionary Databases => _databases; + internal void SetPassword(ParameterResource password) + { + ArgumentNullException.ThrowIfNull(password); + + PasswordParameter = password; + } + + internal void SetUserName(ParameterResource userName) + { + ArgumentNullException.ThrowIfNull(userName); + + UserNameParameter = userName; + } + internal void AddDatabase(string name, string databaseName) { _databases.TryAdd(name, databaseName); diff --git a/tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs index e8c90e020b6..94e55005f5d 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Net.Sockets; using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Utils; using Microsoft.Extensions.DependencyInjection; @@ -327,6 +328,63 @@ public async Task AddAzurePostgresFlexibleServerRunAsContainerProducesCorrectCon Assert.EndsWith("Database=db2Name", db2ConnectionString); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task AddAzurePostgresFlexibleServerRunAsContainerProducesCorrectUserNameAndPasswordAndHost(bool addDbBeforeRunAsContainer) + { + using var builder = TestDistributedApplicationBuilder.Create(); + + var postgres = builder.AddAzurePostgresFlexibleServer("postgres-data"); + var pass = builder.AddParameter("pass", "p@ssw0rd1"); + var user = builder.AddParameter("user", "user1"); + + IResourceBuilder db1 = null!; + IResourceBuilder db2 = null!; + if (addDbBeforeRunAsContainer) + { + db1 = postgres.AddDatabase("db1"); + db2 = postgres.AddDatabase("db2", "db2Name"); + + } + + IResourceBuilder? innerPostgres = null; + postgres.RunAsContainer(configureContainer: c => + { + c.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 12455)); + c.WithHostPort(12455) + .WithPassword(pass) + .WithUserName(user); + innerPostgres = c; + }); + + if (!addDbBeforeRunAsContainer) + { + db1 = postgres.AddDatabase("db1"); + db2 = postgres.AddDatabase("db2", "db2Name"); + } + + Assert.NotNull(innerPostgres); + + var endpoint = Assert.Single(innerPostgres.Resource.Annotations.OfType()); + Assert.Equal(5432, endpoint.TargetPort); + Assert.False(endpoint.IsExternal); + Assert.Equal("tcp", endpoint.Name); + Assert.Equal(12455, endpoint.Port); + Assert.Equal(ProtocolType.Tcp, endpoint.Protocol); + Assert.Equal("tcp", endpoint.Transport); + Assert.Equal("tcp", endpoint.UriScheme); + + Assert.True(postgres.Resource.IsContainer(), "The resource should now be a container resource."); + Assert.Equal("Host=localhost;Port=12455;Username=user1;Password=p@ssw0rd1", await postgres.Resource.ConnectionStringExpression.GetValueAsync(CancellationToken.None)); + + var db1ConnectionString = await db1.Resource.ConnectionStringExpression.GetValueAsync(CancellationToken.None); + Assert.Equal("Host=localhost;Port=12455;Username=user1;Password=p@ssw0rd1;Database=db1", db1ConnectionString); + + var db2ConnectionString = await db2.Resource.ConnectionStringExpression.GetValueAsync(CancellationToken.None); + Assert.Equal("Host=localhost;Port=12455;Username=user1;Password=p@ssw0rd1;Database=db2Name", db2ConnectionString); + } + [Theory] [InlineData(true)] [InlineData(false)] From 6395a7ab434aed4d0987dba852e33010796da386 Mon Sep 17 00:00:00 2001 From: Alireza Baloochi Date: Thu, 3 Apr 2025 00:08:50 +0330 Subject: [PATCH 12/17] Add WithHost,WithPassword for RedisServerResource --- .../RedisBuilderExtensions.cs | 42 ++++++++++++++++++- src/Aspire.Hosting.Redis/RedisResource.cs | 9 +++- .../AzureRedisExtensionsTests.cs | 35 ++++++++++++++++ 3 files changed, 83 insertions(+), 3 deletions(-) diff --git a/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs b/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs index 35b3a167956..baf1480519b 100644 --- a/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs +++ b/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs @@ -21,6 +21,7 @@ namespace Aspire.Hosting; /// public static class RedisBuilderExtensions { + private const string PasswordEnvVarName = "REDIS_PASSWORD"; /// /// Adds a Redis container to the application model. /// @@ -101,7 +102,7 @@ public static IResourceBuilder AddRedis( { if (redis.PasswordParameter is { } password) { - context.EnvironmentVariables["REDIS_PASSWORD"] = password; + context.EnvironmentVariables[PasswordEnvVarName] = password; } }) .WithArgs(context => @@ -114,7 +115,7 @@ public static IResourceBuilder AddRedis( if (redis.PasswordParameter is not null) { redisCommand.Add("--requirepass"); - redisCommand.Add("$REDIS_PASSWORD"); + redisCommand.Add($"${PasswordEnvVarName}"); } if (redis.TryGetLastAnnotation(out var persistenceAnnotation)) @@ -599,4 +600,41 @@ public static IResourceBuilder WithDataBindMount(this IRes return builder.WithBindMount(source, "/data"); } + + /// + /// TODO + /// + /// + /// + /// + public static IResourceBuilder WithPassword(this IResourceBuilder builder, IResourceBuilder password) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(password); + + builder.Resource.SetPassword(password.Resource); + return builder.WithEnvironment(context => + { + if (builder.Resource.PasswordParameter is { } password) + { + context.EnvironmentVariables[PasswordEnvVarName] = password; + } + }); + } + + /// + /// TODO + /// + /// + /// + /// + public static IResourceBuilder WithHostPort(this IResourceBuilder builder, int port) + { + ArgumentNullException.ThrowIfNull(builder); + return builder.WithEndpoint(RedisResource.PrimaryEndpointName, endpoint => + { + endpoint.Port = port; + }); + + } } diff --git a/src/Aspire.Hosting.Redis/RedisResource.cs b/src/Aspire.Hosting.Redis/RedisResource.cs index fc9ea4b2843..2d3769de7ba 100644 --- a/src/Aspire.Hosting.Redis/RedisResource.cs +++ b/src/Aspire.Hosting.Redis/RedisResource.cs @@ -31,7 +31,7 @@ public RedisResource(string name, ParameterResource password) : this(name) /// /// Gets the parameter that contains the Redis server password. /// - public ParameterResource? PasswordParameter { get; } + public ParameterResource? PasswordParameter { get; private set; } private ReferenceExpression BuildConnectionString() { @@ -76,4 +76,11 @@ public ReferenceExpression ConnectionStringExpression return BuildConnectionString().GetValueAsync(cancellationToken); } + + internal void SetPassword(ParameterResource password) + { + ArgumentNullException.ThrowIfNull(password); + + PasswordParameter = password; + } } diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureRedisExtensionsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureRedisExtensionsTests.cs index 53dfe5b9770..e572c34e313 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureRedisExtensionsTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureRedisExtensionsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Net.Sockets; using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Utils; using Microsoft.Extensions.DependencyInjection; @@ -196,6 +197,40 @@ public async Task AddAzureRedisRunAsContainerProducesCorrectConnectionString() Assert.Equal($"localhost:12455,password={redisResource.PasswordParameter.Value}", await redis.Resource.ConnectionStringExpression.GetValueAsync(CancellationToken.None)); } + [Fact] + public async Task AddAzureRedisRunAsContainerProducesCorrectHostAndPassword() + { + using var builder = TestDistributedApplicationBuilder.Create(); + var pass = builder.AddParameter("pass", "p@ssw0rd1"); + + RedisResource? redisResource = null; + var redis = builder.AddAzureRedis("cache") + .RunAsContainer(c => + { + redisResource = c.Resource; + + c.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 12455)); + c.WithHostPort(12455) + .WithPassword(pass); + }); + + Assert.NotNull(redisResource); + + var endpoint = Assert.Single(redisResource.Annotations.OfType()); + Assert.Equal(6379, endpoint.TargetPort); + Assert.False(endpoint.IsExternal); + Assert.Equal("tcp", endpoint.Name); + Assert.Equal(12455, endpoint.Port); + Assert.Equal(ProtocolType.Tcp, endpoint.Protocol); + Assert.Equal("tcp", endpoint.Transport); + Assert.Equal("tcp", endpoint.UriScheme); + + Assert.True(redis.Resource.IsContainer(), "The resource should now be a container resource."); + + Assert.NotNull(redisResource?.PasswordParameter); + Assert.Equal($"localhost:12455,password=p@ssw0rd1", await redis.Resource.ConnectionStringExpression.GetValueAsync(CancellationToken.None)); + } + [Theory] [InlineData(true)] [InlineData(false)] From f3dbd6da8469a0201ea5e97a4b9a7c8e99eafe23 Mon Sep 17 00:00:00 2001 From: Alireza Baloochi Date: Thu, 3 Apr 2025 00:12:27 +0330 Subject: [PATCH 13/17] Add PasswordEnvVarName const for SqlServerBuilder --- src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs b/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs index 53a24d29041..70d8093f570 100644 --- a/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs +++ b/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs @@ -17,6 +17,9 @@ namespace Aspire.Hosting; /// public static partial class SqlServerBuilderExtensions { + + private const string PasswordEnvVarName = "MSSQL_SA_PASSWORD"; + // GO delimiter format: {spaces?}GO{spaces?}{repeat?}{comment?} // https://learn.microsoft.com/sql/t-sql/language-elements/sql-server-utilities-statements-go [GeneratedRegex(@"^\s*GO(?\s+\d{1,6})?(\s*\-{2,}.*)?\s*$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)] @@ -86,7 +89,7 @@ public static IResourceBuilder AddSqlServer(this IDistr .WithEnvironment("ACCEPT_EULA", "Y") .WithEnvironment(context => { - context.EnvironmentVariables["MSSQL_SA_PASSWORD"] = sqlServer.PasswordParameter; + context.EnvironmentVariables[PasswordEnvVarName] = sqlServer.PasswordParameter; }) .WithHealthCheck(healthCheckKey); } @@ -214,7 +217,7 @@ public static IResourceBuilder WithPassword(this IResou builder.Resource.SetPassword(password.Resource); return builder.WithEnvironment(context => { - context.EnvironmentVariables["MSSQL_SA_PASSWORD"] = builder.Resource.PasswordParameter; + context.EnvironmentVariables[PasswordEnvVarName] = builder.Resource.PasswordParameter; }); } From da00ccf08c46e2f9bee16a2426afb98be728e26b Mon Sep 17 00:00:00 2001 From: Alireza Baloochi Date: Thu, 3 Apr 2025 00:35:37 +0330 Subject: [PATCH 14/17] Fix xml docs --- .../PostgresBuilderExtensions.cs | 26 +++++++++---------- .../RedisBuilderExtensions.cs | 16 ++++++------ .../SqlServerBuilderExtensions.cs | 16 ++++++------ 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs b/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs index 0c384ed7f0d..1988f6495c6 100644 --- a/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs +++ b/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs @@ -425,11 +425,11 @@ public static IResourceBuilder WithCreationScript(this } /// - /// TODO + /// Configures the password that the PostgreSQL resource is used. /// - /// - /// - /// + /// The resource builder. + /// The parameter used to provide the password for the PostgreSQL resource. + /// The . public static IResourceBuilder WithPassword(this IResourceBuilder builder, IResourceBuilder password) { ArgumentNullException.ThrowIfNull(builder); @@ -443,11 +443,11 @@ public static IResourceBuilder WithPassword(this IResour } /// - /// TODO + /// Configures the user name that the PostgreSQL resource is used. /// - /// - /// - /// + /// The resource builder. + /// The parameter used to provide the user name for the PostgreSQL resource. + /// The . public static IResourceBuilder WithUserName(this IResourceBuilder builder, IResourceBuilder userName) { ArgumentNullException.ThrowIfNull(builder); @@ -461,12 +461,12 @@ public static IResourceBuilder WithUserName(this IResour } /// - /// TODO + /// Configures the host port that the PostgreSQL resource is exposed on instead of using randomly assigned port. /// - /// - /// - /// - public static IResourceBuilder WithHostPort(this IResourceBuilder builder, int port) + /// The resource builder. + /// The port to bind on the host. If is used random port will be assigned. + /// The . + public static IResourceBuilder WithHostPort(this IResourceBuilder builder, int? port) { ArgumentNullException.ThrowIfNull(builder); return builder.WithEndpoint(PostgresServerResource.PrimaryEndpointName, endpoint => diff --git a/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs b/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs index baf1480519b..f389a9f6d9d 100644 --- a/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs +++ b/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs @@ -602,11 +602,11 @@ public static IResourceBuilder WithDataBindMount(this IRes } /// - /// TODO + /// Configures the password that the Redis resource is used. /// - /// - /// - /// + /// The resource builder. + /// The parameter used to provide the password for the Redis resource. + /// The . public static IResourceBuilder WithPassword(this IResourceBuilder builder, IResourceBuilder password) { ArgumentNullException.ThrowIfNull(builder); @@ -623,11 +623,11 @@ public static IResourceBuilder WithPassword(this IResourceBuilder } /// - /// TODO + /// Configures the host port that the Redis resource is exposed on instead of using randomly assigned port. /// - /// - /// - /// + /// The resource builder. + /// The port to bind on the host. If is used random port will be assigned. + /// The . public static IResourceBuilder WithHostPort(this IResourceBuilder builder, int port) { ArgumentNullException.ThrowIfNull(builder); diff --git a/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs b/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs index 70d8093f570..f025b8f464b 100644 --- a/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs +++ b/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs @@ -204,11 +204,11 @@ public static IResourceBuilder WithCreationScript(thi } /// - /// TODO + /// Configures the password that the SqlServer resource is used. /// - /// - /// - /// + /// The resource builder. + /// The parameter used to provide the password for the SqlServer resource. + /// The . public static IResourceBuilder WithPassword(this IResourceBuilder builder, IResourceBuilder password) { ArgumentNullException.ThrowIfNull(builder); @@ -222,11 +222,11 @@ public static IResourceBuilder WithPassword(this IResou } /// - /// TODO + /// Configures the host port that the SqlServer resource is exposed on instead of using randomly assigned port. /// - /// - /// - /// + /// The resource builder. + /// The port to bind on the host. If is used random port will be assigned. + /// The . public static IResourceBuilder WithHostPort(this IResourceBuilder builder, int port) { ArgumentNullException.ThrowIfNull(builder); From 12056d4c05a784ac9ae81bcec132098e638c4e58 Mon Sep 17 00:00:00 2001 From: Alireza Baloochi Date: Thu, 3 Apr 2025 01:05:53 +0330 Subject: [PATCH 15/17] Add more tests --- .../AddPostgresTests.cs | 40 +++++++++++++++++++ .../AddRedisTests.cs | 36 +++++++++++++++++ .../AddSqlServerTests.cs | 34 ++++++++++++++++ 3 files changed, 110 insertions(+) diff --git a/tests/Aspire.Hosting.PostgreSQL.Tests/AddPostgresTests.cs b/tests/Aspire.Hosting.PostgreSQL.Tests/AddPostgresTests.cs index 116cc46a792..2039d6af15a 100644 --- a/tests/Aspire.Hosting.PostgreSQL.Tests/AddPostgresTests.cs +++ b/tests/Aspire.Hosting.PostgreSQL.Tests/AddPostgresTests.cs @@ -660,4 +660,44 @@ private static string CreatePgWebBookmarkfileContent(PostgresDatabaseResource po return fileContent; } + + [Fact] + public void VerifyPostgresServerResourceWithHostPort() + { + var builder = DistributedApplication.CreateBuilder(); + builder.AddPostgres("postgres"). + WithHostPort(1000); + + var resource = Assert.Single(builder.Resources.OfType()); + var endpoint = Assert.Single(resource.Annotations.OfType()); + Assert.Equal(1000, endpoint.Port); + } + + [Fact] + public async Task VerifyPostgresServerResourceWithPassword() + { + var builder = DistributedApplication.CreateBuilder(); + var password = "p@ssw0rd1"; + var pass = builder.AddParameter("pass", password); + var postgres = builder.AddPostgres("postgres") + .WithPassword(pass) + .WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 2000)); + + var connectionString = await postgres.Resource.GetConnectionStringAsync(); + Assert.Equal("Host=localhost;Port=2000;Username=postgres;Password=p@ssw0rd1", connectionString); + } + + [Fact] + public async Task VerifyPostgresServerResourceWithUserName() + { + var builder = DistributedApplication.CreateBuilder(); + var user = "user1"; + var pass = builder.AddParameter("user", user); + var postgres = builder.AddPostgres("postgres") + .WithUserName(pass) + .WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 2000)); + + var connectionString = await postgres.Resource.GetConnectionStringAsync(); + Assert.Equal($"Host=localhost;Port=2000;Username=user1;Password={postgres.Resource.PasswordParameter.Value}", connectionString); + } } diff --git a/tests/Aspire.Hosting.Redis.Tests/AddRedisTests.cs b/tests/Aspire.Hosting.Redis.Tests/AddRedisTests.cs index 37f4a0a3d85..4e2fdfd549c 100644 --- a/tests/Aspire.Hosting.Redis.Tests/AddRedisTests.cs +++ b/tests/Aspire.Hosting.Redis.Tests/AddRedisTests.cs @@ -318,6 +318,42 @@ public void WithRedisInsightSupportsChangingHostPort() Assert.Equal(1000, endpoint.Port); } + [Fact] + public void VerifyRedisResourceWithHostPort() + { + var builder = DistributedApplication.CreateBuilder(); + builder.AddRedis("myredis"). + WithHostPort(1000); + + var resource = Assert.Single(builder.Resources.OfType()); + var endpoint = Assert.Single(resource.Annotations.OfType()); + Assert.Equal(1000, endpoint.Port); + } + + [Fact] + public async Task VerifyRedisResourceWithPassword() + { + using var builder = TestDistributedApplicationBuilder.Create(); + + var password = "p@ssw0rd1"; + var pass = builder.AddParameter("pass", password); + var redis = builder. + AddRedis("myRedis") + .WithPassword(pass) + .WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 5001)); + + using var app = builder.Build(); + + var appModel = app.Services.GetRequiredService(); + + var containerResource = Assert.Single(appModel.Resources.OfType()); + + var connectionStringResource = Assert.Single(appModel.Resources.OfType()); + var connectionString = await connectionStringResource.GetConnectionStringAsync(default); + 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 SingleRedisInstanceWithoutPasswordProducesCorrectRedisHostsVariable() { diff --git a/tests/Aspire.Hosting.SqlServer.Tests/AddSqlServerTests.cs b/tests/Aspire.Hosting.SqlServer.Tests/AddSqlServerTests.cs index 2a130baa493..9c8b1f71d8f 100644 --- a/tests/Aspire.Hosting.SqlServer.Tests/AddSqlServerTests.cs +++ b/tests/Aspire.Hosting.SqlServer.Tests/AddSqlServerTests.cs @@ -249,4 +249,38 @@ public void CanAddDatabasesWithTheSameNameOnMultipleServers() Assert.Equal("{sqlserver1.connectionString};Initial Catalog=imports", db1.Resource.ConnectionStringExpression.ValueExpression); Assert.Equal("{sqlserver2.connectionString};Initial Catalog=imports", db2.Resource.ConnectionStringExpression.ValueExpression); } + + [Fact] + public void VerifySqlServerServerResourceWithHostPort() + { + var builder = DistributedApplication.CreateBuilder(); + builder.AddSqlServer("sqlserver1"). + WithHostPort(1000); + + var resource = Assert.Single(builder.Resources.OfType()); + var endpoint = Assert.Single(resource.Annotations.OfType()); + Assert.Equal(1000, endpoint.Port); + } + + [Fact] + public async Task VerifySqlServerServerResourceWithPassword() + { + var appBuilder = DistributedApplication.CreateBuilder(); + + var pass = appBuilder.AddParameter("pass", "p@ssw0rd1"); + appBuilder + .AddSqlServer("sqlserver") + .WithPassword(pass) + .WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 1433)); + + using var app = appBuilder.Build(); + + var appModel = app.Services.GetRequiredService(); + + var connectionStringResource = Assert.Single(appModel.Resources.OfType()); + var connectionString = await connectionStringResource.GetConnectionStringAsync(default); + + Assert.Equal("Server=127.0.0.1,1433;User ID=sa;Password=p@ssw0rd1;TrustServerCertificate=true", connectionString); + Assert.Equal("Server={sqlserver.bindings.tcp.host},{sqlserver.bindings.tcp.port};User ID=sa;Password={pass.value};TrustServerCertificate=true", connectionStringResource.ConnectionStringExpression.ValueExpression); + } } From 8fc5950f38b9c67c2b4013e7daa993b4d1224309 Mon Sep 17 00:00:00 2001 From: Alireza Baloochi Date: Fri, 11 Apr 2025 16:32:22 +0330 Subject: [PATCH 16/17] Address PR feedback --- .../PostgresBuilderExtensions.cs | 14 ++++---------- .../PostgresServerResource.cs | 14 -------------- src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs | 8 +------- .../SqlServerBuilderExtensions.cs | 5 +---- .../AddPostgresTests.cs | 4 ++-- tests/Aspire.Hosting.Redis.Tests/AddRedisTests.cs | 12 +++++------- .../AddSqlServerTests.cs | 6 ++---- 7 files changed, 15 insertions(+), 48 deletions(-) diff --git a/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs b/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs index 1988f6495c6..1ff4a9e8ba8 100644 --- a/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs +++ b/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs @@ -435,11 +435,8 @@ public static IResourceBuilder WithPassword(this IResour ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(password); - builder.Resource.SetPassword(password.Resource); - return builder.WithEnvironment(context => - { - context.EnvironmentVariables[PasswordEnvVarName] = builder.Resource.PasswordParameter; - }); + builder.Resource.PasswordParameter = password.Resource; + return builder; } /// @@ -453,11 +450,8 @@ public static IResourceBuilder WithUserName(this IResour ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(userName); - builder.Resource.SetUserName(userName.Resource); - return builder.WithEnvironment(context => - { - context.EnvironmentVariables[UserEnvVarName] = builder.Resource.UserNameReference; - }); + builder.Resource.UserNameParameter = userName.Resource; + return builder; } /// diff --git a/src/Aspire.Hosting.PostgreSQL/PostgresServerResource.cs b/src/Aspire.Hosting.PostgreSQL/PostgresServerResource.cs index 70cd01f9874..fb34562a4e2 100644 --- a/src/Aspire.Hosting.PostgreSQL/PostgresServerResource.cs +++ b/src/Aspire.Hosting.PostgreSQL/PostgresServerResource.cs @@ -88,20 +88,6 @@ public ReferenceExpression ConnectionStringExpression /// public IReadOnlyDictionary Databases => _databases; - internal void SetPassword(ParameterResource password) - { - ArgumentNullException.ThrowIfNull(password); - - PasswordParameter = password; - } - - internal void SetUserName(ParameterResource userName) - { - ArgumentNullException.ThrowIfNull(userName); - - UserNameParameter = userName; - } - internal void AddDatabase(string name, string databaseName) { _databases.TryAdd(name, databaseName); diff --git a/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs b/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs index f389a9f6d9d..b283262d023 100644 --- a/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs +++ b/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs @@ -613,13 +613,7 @@ public static IResourceBuilder WithPassword(this IResourceBuilder ArgumentNullException.ThrowIfNull(password); builder.Resource.SetPassword(password.Resource); - return builder.WithEnvironment(context => - { - if (builder.Resource.PasswordParameter is { } password) - { - context.EnvironmentVariables[PasswordEnvVarName] = password; - } - }); + return builder; } /// diff --git a/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs b/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs index f025b8f464b..b4597d779e1 100644 --- a/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs +++ b/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs @@ -215,10 +215,7 @@ public static IResourceBuilder WithPassword(this IResou ArgumentNullException.ThrowIfNull(password); builder.Resource.SetPassword(password.Resource); - return builder.WithEnvironment(context => - { - context.EnvironmentVariables[PasswordEnvVarName] = builder.Resource.PasswordParameter; - }); + return builder; } /// diff --git a/tests/Aspire.Hosting.PostgreSQL.Tests/AddPostgresTests.cs b/tests/Aspire.Hosting.PostgreSQL.Tests/AddPostgresTests.cs index 2039d6af15a..b728585dac0 100644 --- a/tests/Aspire.Hosting.PostgreSQL.Tests/AddPostgresTests.cs +++ b/tests/Aspire.Hosting.PostgreSQL.Tests/AddPostgresTests.cs @@ -665,8 +665,8 @@ private static string CreatePgWebBookmarkfileContent(PostgresDatabaseResource po public void VerifyPostgresServerResourceWithHostPort() { var builder = DistributedApplication.CreateBuilder(); - builder.AddPostgres("postgres"). - WithHostPort(1000); + builder.AddPostgres("postgres") + .WithHostPort(1000); var resource = Assert.Single(builder.Resources.OfType()); var endpoint = Assert.Single(resource.Annotations.OfType()); diff --git a/tests/Aspire.Hosting.Redis.Tests/AddRedisTests.cs b/tests/Aspire.Hosting.Redis.Tests/AddRedisTests.cs index 4e2fdfd549c..15fc463e91e 100644 --- a/tests/Aspire.Hosting.Redis.Tests/AddRedisTests.cs +++ b/tests/Aspire.Hosting.Redis.Tests/AddRedisTests.cs @@ -322,8 +322,8 @@ public void WithRedisInsightSupportsChangingHostPort() public void VerifyRedisResourceWithHostPort() { var builder = DistributedApplication.CreateBuilder(); - builder.AddRedis("myredis"). - WithHostPort(1000); + builder.AddRedis("myredis") + .WithHostPort(1000); var resource = Assert.Single(builder.Resources.OfType()); var endpoint = Assert.Single(resource.Annotations.OfType()); @@ -337,15 +337,13 @@ public async Task VerifyRedisResourceWithPassword() var password = "p@ssw0rd1"; var pass = builder.AddParameter("pass", password); - var redis = builder. - AddRedis("myRedis") + var redis = builder + .AddRedis("myRedis") .WithPassword(pass) .WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 5001)); using var app = builder.Build(); - var appModel = app.Services.GetRequiredService(); - var containerResource = Assert.Single(appModel.Resources.OfType()); var connectionStringResource = Assert.Single(appModel.Resources.OfType()); @@ -409,7 +407,7 @@ public async Task MultipleRedisInstanceProducesCorrectRedisHostsVariable() redis1.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 5001)); redis2.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 5002, "host2")); - await builder.Eventing.PublishAsync(new (app.Services, app.Services.GetRequiredService())); + await builder.Eventing.PublishAsync(new(app.Services, app.Services.GetRequiredService())); var commander = builder.Resources.Single(r => r.Name.EndsWith("-commander")); diff --git a/tests/Aspire.Hosting.SqlServer.Tests/AddSqlServerTests.cs b/tests/Aspire.Hosting.SqlServer.Tests/AddSqlServerTests.cs index 9c8b1f71d8f..673cc2d2638 100644 --- a/tests/Aspire.Hosting.SqlServer.Tests/AddSqlServerTests.cs +++ b/tests/Aspire.Hosting.SqlServer.Tests/AddSqlServerTests.cs @@ -254,8 +254,8 @@ public void CanAddDatabasesWithTheSameNameOnMultipleServers() public void VerifySqlServerServerResourceWithHostPort() { var builder = DistributedApplication.CreateBuilder(); - builder.AddSqlServer("sqlserver1"). - WithHostPort(1000); + builder.AddSqlServer("sqlserver1") + .WithHostPort(1000); var resource = Assert.Single(builder.Resources.OfType()); var endpoint = Assert.Single(resource.Annotations.OfType()); @@ -274,12 +274,10 @@ public async Task VerifySqlServerServerResourceWithPassword() .WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 1433)); using var app = appBuilder.Build(); - var appModel = app.Services.GetRequiredService(); var connectionStringResource = Assert.Single(appModel.Resources.OfType()); var connectionString = await connectionStringResource.GetConnectionStringAsync(default); - Assert.Equal("Server=127.0.0.1,1433;User ID=sa;Password=p@ssw0rd1;TrustServerCertificate=true", connectionString); Assert.Equal("Server={sqlserver.bindings.tcp.host},{sqlserver.bindings.tcp.port};User ID=sa;Password={pass.value};TrustServerCertificate=true", connectionStringResource.ConnectionStringExpression.ValueExpression); } From 47ecfed9d3448e3552895e8fcb7439d8262ed8db Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Mon, 14 Apr 2025 10:21:13 -0500 Subject: [PATCH 17/17] Minor cleanup Revert unnecessary changes --- .config/CredScanSuppressions.json | 4 ---- .../PostgresBuilderExtensions.cs | 1 - src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs | 5 ++--- .../SqlServerBuilderExtensions.cs | 6 +----- .../AzurePostgresExtensionsTests.cs | 9 ++++----- .../AzureRedisExtensionsTests.cs | 6 +++--- .../AzureSqlExtensionsTests.cs | 7 +++---- 7 files changed, 13 insertions(+), 25 deletions(-) diff --git a/.config/CredScanSuppressions.json b/.config/CredScanSuppressions.json index b9fe2831201..0b52abb9deb 100644 --- a/.config/CredScanSuppressions.json +++ b/.config/CredScanSuppressions.json @@ -36,10 +36,6 @@ { "file": "\\tests\\Shared\\TestCertificates\\testCert.pfx", "_justification": "Legitimate UT certificate file with private key, from dotnet/aspnetcore" - }, - { - "placeholder": "user1", - "_justification": "This is a fake username, used in tests." } ] } diff --git a/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs b/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs index 1ff4a9e8ba8..8c1d873a11b 100644 --- a/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs +++ b/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs @@ -467,7 +467,6 @@ public static IResourceBuilder WithHostPort(this IResour { endpoint.Port = port; }); - } private static IEnumerable WritePgWebBookmarks(IEnumerable postgresInstances) diff --git a/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs b/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs index b283262d023..a07f544f4e8 100644 --- a/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs +++ b/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs @@ -21,7 +21,6 @@ namespace Aspire.Hosting; /// public static class RedisBuilderExtensions { - private const string PasswordEnvVarName = "REDIS_PASSWORD"; /// /// Adds a Redis container to the application model. /// @@ -102,7 +101,7 @@ public static IResourceBuilder AddRedis( { if (redis.PasswordParameter is { } password) { - context.EnvironmentVariables[PasswordEnvVarName] = password; + context.EnvironmentVariables["REDIS_PASSWORD"] = password; } }) .WithArgs(context => @@ -115,7 +114,7 @@ public static IResourceBuilder AddRedis( if (redis.PasswordParameter is not null) { redisCommand.Add("--requirepass"); - redisCommand.Add($"${PasswordEnvVarName}"); + redisCommand.Add("$REDIS_PASSWORD"); } if (redis.TryGetLastAnnotation(out var persistenceAnnotation)) diff --git a/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs b/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs index b4597d779e1..eeb28a02c88 100644 --- a/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs +++ b/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs @@ -17,9 +17,6 @@ namespace Aspire.Hosting; /// public static partial class SqlServerBuilderExtensions { - - private const string PasswordEnvVarName = "MSSQL_SA_PASSWORD"; - // GO delimiter format: {spaces?}GO{spaces?}{repeat?}{comment?} // https://learn.microsoft.com/sql/t-sql/language-elements/sql-server-utilities-statements-go [GeneratedRegex(@"^\s*GO(?\s+\d{1,6})?(\s*\-{2,}.*)?\s*$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)] @@ -89,7 +86,7 @@ public static IResourceBuilder AddSqlServer(this IDistr .WithEnvironment("ACCEPT_EULA", "Y") .WithEnvironment(context => { - context.EnvironmentVariables[PasswordEnvVarName] = sqlServer.PasswordParameter; + context.EnvironmentVariables["MSSQL_SA_PASSWORD"] = sqlServer.PasswordParameter; }) .WithHealthCheck(healthCheckKey); } @@ -231,7 +228,6 @@ public static IResourceBuilder WithHostPort(this IResou { endpoint.Port = port; }); - } private static async Task CreateDatabaseAsync(SqlConnection sqlConnection, SqlServerDatabaseResource sqlDatabase, IServiceProvider serviceProvider, CancellationToken ct) diff --git a/tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs index 673ac0d5e7e..1263d8b6fa8 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs @@ -359,16 +359,15 @@ public async Task AddAzurePostgresFlexibleServerRunAsContainerProducesCorrectUse { db1 = postgres.AddDatabase("db1"); db2 = postgres.AddDatabase("db2", "db2Name"); - } IResourceBuilder? innerPostgres = null; postgres.RunAsContainer(configureContainer: c => { - c.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 12455)); - c.WithHostPort(12455) - .WithPassword(pass) - .WithUserName(user); + c.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 12455)) + .WithHostPort(12455) + .WithPassword(pass) + .WithUserName(user); innerPostgres = c; }); diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureRedisExtensionsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureRedisExtensionsTests.cs index bde4d15baf8..283d72fe056 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureRedisExtensionsTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureRedisExtensionsTests.cs @@ -223,9 +223,9 @@ public async Task AddAzureRedisRunAsContainerProducesCorrectHostAndPassword() { redisResource = c.Resource; - c.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 12455)); - c.WithHostPort(12455) - .WithPassword(pass); + c.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 12455)) + .WithHostPort(12455) + .WithPassword(pass); }); Assert.NotNull(redisResource); diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureSqlExtensionsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureSqlExtensionsTests.cs index 2d365a773b2..fcaf88ede4b 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureSqlExtensionsTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureSqlExtensionsTests.cs @@ -205,15 +205,14 @@ public async Task AddAzureSqlServerRunAsContainerProducesCorrectPasswordAndPort( { db1 = sql.AddDatabase("db1"); db2 = sql.AddDatabase("db2", "db2Name"); - } IResourceBuilder? innerSql = null; sql.RunAsContainer(configureContainer: c => { - c.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 12455)); - c.WithHostPort(12455) - .WithPassword(pass); + c.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 12455)) + .WithHostPort(12455) + .WithPassword(pass); innerSql = c; });