Skip to content

Commit 6ff4a88

Browse files
Alirexaasebastienroseerhardt
authored
Add ability to configure Port and Password for Redis and Postgres and SqlServer when they configured to run by RunAsContainer (#8439)
* Provide port and password for AzureSql RunAsContainer * Add tests * Revert playground app changes * suppress api-diff * Add new overload * Fix build * Update src/Aspire.Hosting.Azure.Sql/RunAsContainerOptions.cs Co-authored-by: Sébastien Ros <[email protected]> * Add WithHostPort and WithPassword to SqlServer * Revert AzureSqlExtensions * Make SetPassword internal * Add WithHost,WithPassword and WithUserName for PostgresServerResource * Add WithHost,WithPassword for RedisServerResource * Add PasswordEnvVarName const for SqlServerBuilder * Fix xml docs * Add more tests * Address PR feedback * Minor cleanup Revert unnecessary changes --------- Co-authored-by: Sébastien Ros <[email protected]> Co-authored-by: Eric Erhardt <[email protected]>
1 parent 0e84a68 commit 6ff4a88

File tree

11 files changed

+375
-2
lines changed

11 files changed

+375
-2
lines changed

src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,51 @@ public static IResourceBuilder<PostgresDatabaseResource> WithCreationScript(this
424424
return builder;
425425
}
426426

427+
/// <summary>
428+
/// Configures the password that the PostgreSQL resource is used.
429+
/// </summary>
430+
/// <param name="builder">The resource builder.</param>
431+
/// <param name="password">The parameter used to provide the password for the PostgreSQL resource.</param>
432+
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
433+
public static IResourceBuilder<PostgresServerResource> WithPassword(this IResourceBuilder<PostgresServerResource> builder, IResourceBuilder<ParameterResource> password)
434+
{
435+
ArgumentNullException.ThrowIfNull(builder);
436+
ArgumentNullException.ThrowIfNull(password);
437+
438+
builder.Resource.PasswordParameter = password.Resource;
439+
return builder;
440+
}
441+
442+
/// <summary>
443+
/// Configures the user name that the PostgreSQL resource is used.
444+
/// </summary>
445+
/// <param name="builder">The resource builder.</param>
446+
/// <param name="userName">The parameter used to provide the user name for the PostgreSQL resource.</param>
447+
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
448+
public static IResourceBuilder<PostgresServerResource> WithUserName(this IResourceBuilder<PostgresServerResource> builder, IResourceBuilder<ParameterResource> userName)
449+
{
450+
ArgumentNullException.ThrowIfNull(builder);
451+
ArgumentNullException.ThrowIfNull(userName);
452+
453+
builder.Resource.UserNameParameter = userName.Resource;
454+
return builder;
455+
}
456+
457+
/// <summary>
458+
/// Configures the host port that the PostgreSQL resource is exposed on instead of using randomly assigned port.
459+
/// </summary>
460+
/// <param name="builder">The resource builder.</param>
461+
/// <param name="port">The port to bind on the host. If <see langword="null"/> is used random port will be assigned.</param>
462+
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
463+
public static IResourceBuilder<PostgresServerResource> WithHostPort(this IResourceBuilder<PostgresServerResource> builder, int? port)
464+
{
465+
ArgumentNullException.ThrowIfNull(builder);
466+
return builder.WithEndpoint(PostgresServerResource.PrimaryEndpointName, endpoint =>
467+
{
468+
endpoint.Port = port;
469+
});
470+
}
471+
427472
private static IEnumerable<ContainerFileSystemItem> WritePgWebBookmarks(IEnumerable<PostgresDatabaseResource> postgresInstances)
428473
{
429474
var bookmarkFiles = new List<ContainerFileSystemItem>();

src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,4 +412,35 @@ public static IResourceBuilder<RedisInsightResource> WithDataBindMount(this IRes
412412

413413
return builder.WithBindMount(source, "/data");
414414
}
415+
416+
/// <summary>
417+
/// Configures the password that the Redis resource is used.
418+
/// </summary>
419+
/// <param name="builder">The resource builder.</param>
420+
/// <param name="password">The parameter used to provide the password for the Redis resource.</param>
421+
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
422+
public static IResourceBuilder<RedisResource> WithPassword(this IResourceBuilder<RedisResource> builder, IResourceBuilder<ParameterResource> password)
423+
{
424+
ArgumentNullException.ThrowIfNull(builder);
425+
ArgumentNullException.ThrowIfNull(password);
426+
427+
builder.Resource.SetPassword(password.Resource);
428+
return builder;
429+
}
430+
431+
/// <summary>
432+
/// Configures the host port that the Redis resource is exposed on instead of using randomly assigned port.
433+
/// </summary>
434+
/// <param name="builder">The resource builder.</param>
435+
/// <param name="port">The port to bind on the host. If <see langword="null"/> is used random port will be assigned.</param>
436+
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
437+
public static IResourceBuilder<RedisResource> WithHostPort(this IResourceBuilder<RedisResource> builder, int port)
438+
{
439+
ArgumentNullException.ThrowIfNull(builder);
440+
return builder.WithEndpoint(RedisResource.PrimaryEndpointName, endpoint =>
441+
{
442+
endpoint.Port = port;
443+
});
444+
445+
}
415446
}

src/Aspire.Hosting.Redis/RedisResource.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public RedisResource(string name, ParameterResource password) : this(name)
3131
/// <summary>
3232
/// Gets the parameter that contains the Redis server password.
3333
/// </summary>
34-
public ParameterResource? PasswordParameter { get; }
34+
public ParameterResource? PasswordParameter { get; private set; }
3535

3636
private ReferenceExpression BuildConnectionString()
3737
{
@@ -76,4 +76,11 @@ public ReferenceExpression ConnectionStringExpression
7676

7777
return BuildConnectionString().GetValueAsync(cancellationToken);
7878
}
79+
80+
internal void SetPassword(ParameterResource password)
81+
{
82+
ArgumentNullException.ThrowIfNull(password);
83+
84+
PasswordParameter = password;
85+
}
7986
}

src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,36 @@ public static IResourceBuilder<SqlServerDatabaseResource> WithCreationScript(thi
200200
return builder;
201201
}
202202

203+
/// <summary>
204+
/// Configures the password that the SqlServer resource is used.
205+
/// </summary>
206+
/// <param name="builder">The resource builder.</param>
207+
/// <param name="password">The parameter used to provide the password for the SqlServer resource.</param>
208+
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
209+
public static IResourceBuilder<SqlServerServerResource> WithPassword(this IResourceBuilder<SqlServerServerResource> builder, IResourceBuilder<ParameterResource> password)
210+
{
211+
ArgumentNullException.ThrowIfNull(builder);
212+
ArgumentNullException.ThrowIfNull(password);
213+
214+
builder.Resource.SetPassword(password.Resource);
215+
return builder;
216+
}
217+
218+
/// <summary>
219+
/// Configures the host port that the SqlServer resource is exposed on instead of using randomly assigned port.
220+
/// </summary>
221+
/// <param name="builder">The resource builder.</param>
222+
/// <param name="port">The port to bind on the host. If <see langword="null"/> is used random port will be assigned.</param>
223+
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
224+
public static IResourceBuilder<SqlServerServerResource> WithHostPort(this IResourceBuilder<SqlServerServerResource> builder, int port)
225+
{
226+
ArgumentNullException.ThrowIfNull(builder);
227+
return builder.WithEndpoint(SqlServerServerResource.PrimaryEndpointName, endpoint =>
228+
{
229+
endpoint.Port = port;
230+
});
231+
}
232+
203233
private static async Task CreateDatabaseAsync(SqlConnection sqlConnection, SqlServerDatabaseResource sqlDatabase, IServiceProvider serviceProvider, CancellationToken ct)
204234
{
205235
try

src/Aspire.Hosting.SqlServer/SqlServerServerResource.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public SqlServerServerResource(string name, ParameterResource password) : base(n
3131
/// <summary>
3232
/// Gets the parameter that contains the SQL Server password.
3333
/// </summary>
34-
public ParameterResource PasswordParameter { get; }
34+
public ParameterResource PasswordParameter { get; private set; }
3535

3636
private ReferenceExpression ConnectionString =>
3737
ReferenceExpression.Create(
@@ -76,6 +76,13 @@ public ReferenceExpression ConnectionStringExpression
7676
/// </summary>
7777
public IReadOnlyDictionary<string, string> Databases => _databases;
7878

79+
internal void SetPassword(ParameterResource password)
80+
{
81+
ArgumentNullException.ThrowIfNull(password);
82+
83+
PasswordParameter = password;
84+
}
85+
7986
internal void AddDatabase(SqlServerDatabaseResource database)
8087
{
8188
_databases.TryAdd(database.Name, database.DatabaseName);

tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Net.Sockets;
45
using Aspire.Hosting.ApplicationModel;
56
using Aspire.Hosting.Utils;
67
using Microsoft.Extensions.DependencyInjection;
@@ -341,6 +342,62 @@ public async Task AddAzurePostgresFlexibleServerRunAsContainerProducesCorrectCon
341342
Assert.EndsWith("Database=db2Name", db2ConnectionString);
342343
}
343344

345+
[Theory]
346+
[InlineData(true)]
347+
[InlineData(false)]
348+
public async Task AddAzurePostgresFlexibleServerRunAsContainerProducesCorrectUserNameAndPasswordAndHost(bool addDbBeforeRunAsContainer)
349+
{
350+
using var builder = TestDistributedApplicationBuilder.Create();
351+
352+
var postgres = builder.AddAzurePostgresFlexibleServer("postgres-data");
353+
var pass = builder.AddParameter("pass", "p@ssw0rd1");
354+
var user = builder.AddParameter("user", "user1");
355+
356+
IResourceBuilder<AzurePostgresFlexibleServerDatabaseResource> db1 = null!;
357+
IResourceBuilder<AzurePostgresFlexibleServerDatabaseResource> db2 = null!;
358+
if (addDbBeforeRunAsContainer)
359+
{
360+
db1 = postgres.AddDatabase("db1");
361+
db2 = postgres.AddDatabase("db2", "db2Name");
362+
}
363+
364+
IResourceBuilder<PostgresServerResource>? innerPostgres = null;
365+
postgres.RunAsContainer(configureContainer: c =>
366+
{
367+
c.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 12455))
368+
.WithHostPort(12455)
369+
.WithPassword(pass)
370+
.WithUserName(user);
371+
innerPostgres = c;
372+
});
373+
374+
if (!addDbBeforeRunAsContainer)
375+
{
376+
db1 = postgres.AddDatabase("db1");
377+
db2 = postgres.AddDatabase("db2", "db2Name");
378+
}
379+
380+
Assert.NotNull(innerPostgres);
381+
382+
var endpoint = Assert.Single(innerPostgres.Resource.Annotations.OfType<EndpointAnnotation>());
383+
Assert.Equal(5432, endpoint.TargetPort);
384+
Assert.False(endpoint.IsExternal);
385+
Assert.Equal("tcp", endpoint.Name);
386+
Assert.Equal(12455, endpoint.Port);
387+
Assert.Equal(ProtocolType.Tcp, endpoint.Protocol);
388+
Assert.Equal("tcp", endpoint.Transport);
389+
Assert.Equal("tcp", endpoint.UriScheme);
390+
391+
Assert.True(postgres.Resource.IsContainer(), "The resource should now be a container resource.");
392+
Assert.Equal("Host=localhost;Port=12455;Username=user1;Password=p@ssw0rd1", await postgres.Resource.ConnectionStringExpression.GetValueAsync(CancellationToken.None));
393+
394+
var db1ConnectionString = await db1.Resource.ConnectionStringExpression.GetValueAsync(CancellationToken.None);
395+
Assert.Equal("Host=localhost;Port=12455;Username=user1;Password=p@ssw0rd1;Database=db1", db1ConnectionString);
396+
397+
var db2ConnectionString = await db2.Resource.ConnectionStringExpression.GetValueAsync(CancellationToken.None);
398+
Assert.Equal("Host=localhost;Port=12455;Username=user1;Password=p@ssw0rd1;Database=db2Name", db2ConnectionString);
399+
}
400+
344401
[Theory]
345402
[InlineData(true)]
346403
[InlineData(false)]

tests/Aspire.Hosting.Azure.Tests/AzureRedisExtensionsTests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Net.Sockets;
45
using Aspire.Hosting.ApplicationModel;
56
using Aspire.Hosting.Utils;
67
using Microsoft.Extensions.DependencyInjection;
@@ -210,6 +211,40 @@ public async Task AddAzureRedisRunAsContainerProducesCorrectConnectionString()
210211
Assert.Equal($"localhost:12455,password={redisResource.PasswordParameter.Value}", await redis.Resource.ConnectionStringExpression.GetValueAsync(CancellationToken.None));
211212
}
212213

214+
[Fact]
215+
public async Task AddAzureRedisRunAsContainerProducesCorrectHostAndPassword()
216+
{
217+
using var builder = TestDistributedApplicationBuilder.Create();
218+
var pass = builder.AddParameter("pass", "p@ssw0rd1");
219+
220+
RedisResource? redisResource = null;
221+
var redis = builder.AddAzureRedis("cache")
222+
.RunAsContainer(c =>
223+
{
224+
redisResource = c.Resource;
225+
226+
c.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 12455))
227+
.WithHostPort(12455)
228+
.WithPassword(pass);
229+
});
230+
231+
Assert.NotNull(redisResource);
232+
233+
var endpoint = Assert.Single(redisResource.Annotations.OfType<EndpointAnnotation>());
234+
Assert.Equal(6379, endpoint.TargetPort);
235+
Assert.False(endpoint.IsExternal);
236+
Assert.Equal("tcp", endpoint.Name);
237+
Assert.Equal(12455, endpoint.Port);
238+
Assert.Equal(ProtocolType.Tcp, endpoint.Protocol);
239+
Assert.Equal("tcp", endpoint.Transport);
240+
Assert.Equal("tcp", endpoint.UriScheme);
241+
242+
Assert.True(redis.Resource.IsContainer(), "The resource should now be a container resource.");
243+
244+
Assert.NotNull(redisResource?.PasswordParameter);
245+
Assert.Equal($"localhost:12455,password=p@ssw0rd1", await redis.Resource.ConnectionStringExpression.GetValueAsync(CancellationToken.None));
246+
}
247+
213248
[Theory]
214249
[InlineData(true)]
215250
[InlineData(false)]

tests/Aspire.Hosting.Azure.Tests/AzureSqlExtensionsTests.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Net.Sockets;
45
using Aspire.Hosting.ApplicationModel;
56
using Aspire.Hosting.Utils;
67
using Xunit;
@@ -189,6 +190,60 @@ public async Task AddAzureSqlServerRunAsContainerProducesCorrectConnectionString
189190
Assert.EndsWith(";TrustServerCertificate=true;Database=db2Name", db2ConnectionString);
190191
}
191192

193+
[Theory]
194+
[InlineData(true)]
195+
[InlineData(false)]
196+
public async Task AddAzureSqlServerRunAsContainerProducesCorrectPasswordAndPort(bool addDbBeforeRunAsContainer)
197+
{
198+
using var builder = TestDistributedApplicationBuilder.Create();
199+
200+
var sql = builder.AddAzureSqlServer("sql");
201+
var pass = builder.AddParameter("pass", "p@ssw0rd1");
202+
IResourceBuilder<AzureSqlDatabaseResource> db1 = null!;
203+
IResourceBuilder<AzureSqlDatabaseResource> db2 = null!;
204+
if (addDbBeforeRunAsContainer)
205+
{
206+
db1 = sql.AddDatabase("db1");
207+
db2 = sql.AddDatabase("db2", "db2Name");
208+
}
209+
210+
IResourceBuilder<SqlServerServerResource>? innerSql = null;
211+
sql.RunAsContainer(configureContainer: c =>
212+
{
213+
c.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 12455))
214+
.WithHostPort(12455)
215+
.WithPassword(pass);
216+
innerSql = c;
217+
});
218+
219+
Assert.NotNull(innerSql);
220+
221+
if (!addDbBeforeRunAsContainer)
222+
{
223+
db1 = sql.AddDatabase("db1");
224+
db2 = sql.AddDatabase("db2", "db2Name");
225+
}
226+
227+
var endpoint = Assert.Single(innerSql.Resource.Annotations.OfType<EndpointAnnotation>());
228+
Assert.Equal(1433, endpoint.TargetPort);
229+
Assert.False(endpoint.IsExternal);
230+
Assert.Equal("tcp", endpoint.Name);
231+
Assert.Equal(12455, endpoint.Port);
232+
Assert.Equal(ProtocolType.Tcp, endpoint.Protocol);
233+
Assert.Equal("tcp", endpoint.Transport);
234+
Assert.Equal("tcp", endpoint.UriScheme);
235+
236+
Assert.True(sql.Resource.IsContainer(), "The resource should now be a container resource.");
237+
var serverConnectionString = await sql.Resource.ConnectionStringExpression.GetValueAsync(CancellationToken.None);
238+
Assert.Equal("Server=127.0.0.1,12455;User ID=sa;Password=p@ssw0rd1;TrustServerCertificate=true", serverConnectionString);
239+
240+
var db1ConnectionString = await db1.Resource.ConnectionStringExpression.GetValueAsync(CancellationToken.None);
241+
Assert.StartsWith("Server=127.0.0.1,12455;User ID=sa;Password=p@ssw0rd1;TrustServerCertificate=true;Database=db1", db1ConnectionString);
242+
243+
var db2ConnectionString = await db2.Resource.ConnectionStringExpression.GetValueAsync(CancellationToken.None);
244+
Assert.StartsWith("Server=127.0.0.1,12455;User ID=sa;Password=p@ssw0rd1;TrustServerCertificate=true;Database=db2Name", db2ConnectionString);
245+
}
246+
192247
[Theory]
193248
[InlineData(true, true)]
194249
[InlineData(true, false)]

0 commit comments

Comments
 (0)