Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 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
4 changes: 4 additions & 0 deletions .config/CredScanSuppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
{
"file": "\\tests\\Shared\\TestCertificates\\testCert.pfx",
"_justification": "Legitimate UT certificate file with private key, from dotnet/aspnetcore"
},
{
"placeholder": "user1",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary?

cc @danmoseley @sebastienros

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure.

"_justification": "This is a fake username, used in tests."
}
]
}
52 changes: 52 additions & 0 deletions src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,58 @@ public static IResourceBuilder<PostgresDatabaseResource> WithCreationScript(this
return builder;
}

/// <summary>
/// Configures the password that the PostgreSQL resource is used.
/// </summary>
/// <param name="builder">The resource builder.</param>
/// <param name="password">The parameter used to provide the password for the PostgreSQL resource.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<PostgresServerResource> WithPassword(this IResourceBuilder<PostgresServerResource> builder, IResourceBuilder<ParameterResource> password)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(password);

builder.Resource.SetPassword(password.Resource);
return builder.WithEnvironment(context =>
{
context.EnvironmentVariables[PasswordEnvVarName] = builder.Resource.PasswordParameter;
});
Copy link
Member

@eerhardt eerhardt Apr 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need this because this code grabs the parameters from the resource, so it will continue to work if we set the parmaeter correctly.

.WithEnvironment(context =>
{
context.EnvironmentVariables[UserEnvVarName] = postgresServer.UserNameReference;
context.EnvironmentVariables[PasswordEnvVarName] = postgresServer.PasswordParameter;

(repeated elsewhere) #Closed

}

/// <summary>
/// Configures the user name that the PostgreSQL resource is used.
/// </summary>
/// <param name="builder">The resource builder.</param>
/// <param name="userName">The parameter used to provide the user name for the PostgreSQL resource.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<PostgresServerResource> WithUserName(this IResourceBuilder<PostgresServerResource> builder, IResourceBuilder<ParameterResource> userName)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(userName);

builder.Resource.SetUserName(userName.Resource);
return builder.WithEnvironment(context =>
{
context.EnvironmentVariables[UserEnvVarName] = builder.Resource.UserNameReference;
});
}

/// <summary>
/// Configures the host port that the PostgreSQL resource is exposed on instead of using randomly assigned port.
/// </summary>
/// <param name="builder">The resource builder.</param>
/// <param name="port">The port to bind on the host. If <see langword="null"/> is used random port will be assigned.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<PostgresServerResource> WithHostPort(this IResourceBuilder<PostgresServerResource> builder, int? port)
{
ArgumentNullException.ThrowIfNull(builder);
return builder.WithEndpoint(PostgresServerResource.PrimaryEndpointName, endpoint =>
{
endpoint.Port = port;
});

}

private static IEnumerable<ContainerFileSystemItem> WritePgWebBookmarks(IEnumerable<PostgresDatabaseResource> postgresInstances)
{
var bookmarkFiles = new List<ContainerFileSystemItem>();
Expand Down
14 changes: 14 additions & 0 deletions src/Aspire.Hosting.PostgreSQL/PostgresServerResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,20 @@ public ReferenceExpression ConnectionStringExpression
/// </summary>
public IReadOnlyDictionary<string, string> Databases => _databases;

internal void SetPassword(ParameterResource password)
{
ArgumentNullException.ThrowIfNull(password);

PasswordParameter = password;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These properties are publically settable. We can remove these methods and just set the property.

}

internal void SetUserName(ParameterResource userName)
{
ArgumentNullException.ThrowIfNull(userName);

UserNameParameter = userName;
}

internal void AddDatabase(string name, string databaseName)
{
_databases.TryAdd(name, databaseName);
Expand Down
42 changes: 40 additions & 2 deletions src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ namespace Aspire.Hosting;
/// </summary>
public static class RedisBuilderExtensions
{
private const string PasswordEnvVarName = "REDIS_PASSWORD";
/// <summary>
/// Adds a Redis container to the application model.
/// </summary>
Expand Down Expand Up @@ -101,7 +102,7 @@ public static IResourceBuilder<RedisResource> AddRedis(
{
if (redis.PasswordParameter is { } password)
{
context.EnvironmentVariables["REDIS_PASSWORD"] = password;
context.EnvironmentVariables[PasswordEnvVarName] = password;
}
})
.WithArgs(context =>
Expand All @@ -114,7 +115,7 @@ public static IResourceBuilder<RedisResource> AddRedis(
if (redis.PasswordParameter is not null)
{
redisCommand.Add("--requirepass");
redisCommand.Add("$REDIS_PASSWORD");
redisCommand.Add($"${PasswordEnvVarName}");
}

if (redis.TryGetLastAnnotation<PersistenceAnnotation>(out var persistenceAnnotation))
Expand Down Expand Up @@ -599,4 +600,41 @@ public static IResourceBuilder<RedisInsightResource> WithDataBindMount(this IRes

return builder.WithBindMount(source, "/data");
}

/// <summary>
/// Configures the password that the Redis resource is used.
/// </summary>
/// <param name="builder">The resource builder.</param>
/// <param name="password">The parameter used to provide the password for the Redis resource.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<RedisResource> WithPassword(this IResourceBuilder<RedisResource> builder, IResourceBuilder<ParameterResource> 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;
}
});
}

/// <summary>
/// Configures the host port that the Redis resource is exposed on instead of using randomly assigned port.
/// </summary>
/// <param name="builder">The resource builder.</param>
/// <param name="port">The port to bind on the host. If <see langword="null"/> is used random port will be assigned.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<RedisResource> WithHostPort(this IResourceBuilder<RedisResource> builder, int port)
{
ArgumentNullException.ThrowIfNull(builder);
return builder.WithEndpoint(RedisResource.PrimaryEndpointName, endpoint =>
{
endpoint.Port = port;
});

}
}
9 changes: 8 additions & 1 deletion src/Aspire.Hosting.Redis/RedisResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public RedisResource(string name, ParameterResource password) : this(name)
/// <summary>
/// Gets the parameter that contains the Redis server password.
/// </summary>
public ParameterResource? PasswordParameter { get; }
public ParameterResource? PasswordParameter { get; private set; }

private ReferenceExpression BuildConnectionString()
{
Expand Down Expand Up @@ -76,4 +76,11 @@ public ReferenceExpression ConnectionStringExpression

return BuildConnectionString().GetValueAsync(cancellationToken);
}

internal void SetPassword(ParameterResource password)
{
ArgumentNullException.ThrowIfNull(password);

PasswordParameter = password;
}
}
39 changes: 38 additions & 1 deletion src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ namespace Aspire.Hosting;
/// </summary>
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(?<repeat>\s+\d{1,6})?(\s*\-{2,}.*)?\s*$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)]
Expand Down Expand Up @@ -86,7 +89,7 @@ public static IResourceBuilder<SqlServerServerResource> AddSqlServer(this IDistr
.WithEnvironment("ACCEPT_EULA", "Y")
.WithEnvironment(context =>
{
context.EnvironmentVariables["MSSQL_SA_PASSWORD"] = sqlServer.PasswordParameter;
context.EnvironmentVariables[PasswordEnvVarName] = sqlServer.PasswordParameter;
})
.WithHealthCheck(healthCheckKey);
}
Expand Down Expand Up @@ -200,6 +203,40 @@ public static IResourceBuilder<SqlServerDatabaseResource> WithCreationScript(thi
return builder;
}

/// <summary>
/// Configures the password that the SqlServer resource is used.
/// </summary>
/// <param name="builder">The resource builder.</param>
/// <param name="password">The parameter used to provide the password for the SqlServer resource.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<SqlServerServerResource> WithPassword(this IResourceBuilder<SqlServerServerResource> builder, IResourceBuilder<ParameterResource> password)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(password);

builder.Resource.SetPassword(password.Resource);
return builder.WithEnvironment(context =>
{
context.EnvironmentVariables[PasswordEnvVarName] = builder.Resource.PasswordParameter;
});
}

/// <summary>
/// Configures the host port that the SqlServer resource is exposed on instead of using randomly assigned port.
/// </summary>
/// <param name="builder">The resource builder.</param>
/// <param name="port">The port to bind on the host. If <see langword="null"/> is used random port will be assigned.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<SqlServerServerResource> WithHostPort(this IResourceBuilder<SqlServerServerResource> 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
Expand Down
9 changes: 8 additions & 1 deletion src/Aspire.Hosting.SqlServer/SqlServerServerResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public SqlServerServerResource(string name, ParameterResource password) : base(n
/// <summary>
/// Gets the parameter that contains the SQL Server password.
/// </summary>
public ParameterResource PasswordParameter { get; }
public ParameterResource PasswordParameter { get; private set; }

private ReferenceExpression ConnectionString =>
ReferenceExpression.Create(
Expand Down Expand Up @@ -76,6 +76,13 @@ public ReferenceExpression ConnectionStringExpression
/// </summary>
public IReadOnlyDictionary<string, string> Databases => _databases;

internal void SetPassword(ParameterResource password)
{
ArgumentNullException.ThrowIfNull(password);

PasswordParameter = password;
}

internal void AddDatabase(SqlServerDatabaseResource database)
{
_databases.TryAdd(database.Name, database.DatabaseName);
Expand Down
58 changes: 58 additions & 0 deletions tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -341,6 +342,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<AzurePostgresFlexibleServerDatabaseResource> db1 = null!;
IResourceBuilder<AzurePostgresFlexibleServerDatabaseResource> db2 = null!;
if (addDbBeforeRunAsContainer)
{
db1 = postgres.AddDatabase("db1");
db2 = postgres.AddDatabase("db2", "db2Name");

}

IResourceBuilder<PostgresServerResource>? 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<EndpointAnnotation>());
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)]
Expand Down
35 changes: 35 additions & 0 deletions tests/Aspire.Hosting.Azure.Tests/AzureRedisExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -210,6 +211,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<EndpointAnnotation>());
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)]
Expand Down
Loading
Loading