Skip to content

Commit

Permalink
Support Postgres user name and password from Parameter
Browse files Browse the repository at this point in the history
Contributes to dotnet#2403
  • Loading branch information
eerhardt committed Mar 14, 2024
1 parent 52cc408 commit c2ace64
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 59 deletions.
4 changes: 2 additions & 2 deletions playground/bicep/BicepSample.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@

var administratorLogin = builder.AddParameter("administratorLogin");
var administratorLoginPassword = builder.AddParameter("administratorLoginPassword", secret: true);
var pg = builder.AddPostgres("postgres2")
.AsAzurePostgresFlexibleServer(administratorLogin, administratorLoginPassword)
var pg = builder.AddPostgres("postgres2", administratorLogin, administratorLoginPassword)
.AsAzurePostgresFlexibleServer()
.AddDatabase("db2");

var cosmosDb = builder.AddAzureCosmosDB("cosmos")
Expand Down
4 changes: 2 additions & 2 deletions playground/cdk/CdkSample.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@

var pgsqlAdministratorLogin = builder.AddParameter("pgsqlAdministratorLogin");
var pgsqlAdministratorLoginPassword = builder.AddParameter("pgsqlAdministratorLoginPassword", secret: true);
var pgsqldb = builder.AddPostgres("pgsql")
.AsAzurePostgresFlexibleServerConstruct(pgsqlAdministratorLogin, pgsqlAdministratorLoginPassword)
var pgsqldb = builder.AddPostgres("pgsql", pgsqlAdministratorLogin, pgsqlAdministratorLoginPassword)
.AsAzurePostgresFlexibleServerConstruct()
.AddDatabase("pgsqldb");

var pgsql2 = builder.AddPostgres("pgsql2").AsAzurePostgresFlexibleServerConstruct();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,21 @@ public static IResourceBuilder<T> WithParameter<T>(this IResourceBuilder<T> buil
public static IResourceBuilder<T> WithParameter<T>(this IResourceBuilder<T> builder, string name, IResourceBuilder<ParameterResource> value)
where T : AzureBicepResource
{
builder.Resource.Parameters[name] = value.Resource;
return builder.WithParameter(name, value.Resource);
}

/// <summary>
/// Adds a parameter to the bicep template.
/// </summary>
/// <typeparam name="T">The <see cref="AzureBicepResource"/></typeparam>
/// <param name="builder">The resource builder.</param>
/// <param name="name">The name of the input.</param>
/// <param name="value">The value of the parameter.</param>
/// <returns>An <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<T> WithParameter<T>(this IResourceBuilder<T> builder, string name, ParameterResource value)
where T : AzureBicepResource
{
builder.Resource.Parameters[name] = value;
return builder;
}

Expand Down
41 changes: 8 additions & 33 deletions src/Aspire.Hosting.Azure/Extensions/AzurePostgresExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,15 @@ public static class AzurePostgresExtensions
/// Configures Postgres resource to be deployed as Azure Postgres Flexible Server when deployed using Azure Developer CLI.
/// </summary>
/// <param name="builder">The builder for the Postgres resource.</param>
/// <param name="administratorLogin">Parameter containing the administrator username for the server that will be provisioned in Azure.</param>
/// <param name="administratorLoginPassword">Parameter containing the administrator password for the server that will be provisioned in Azure.</param>
/// <param name="callback">Callback to customize the Azure resources that will be provisioned in Azure.</param>
/// <returns></returns>
public static IResourceBuilder<PostgresServerResource> PublishAsAzurePostgresFlexibleServer(
this IResourceBuilder<PostgresServerResource> builder,
IResourceBuilder<ParameterResource>? administratorLogin = null,
IResourceBuilder<ParameterResource>? administratorLoginPassword = null,
Action<IResourceBuilder<AzurePostgresResource>>? callback = null)
{
var resource = new AzurePostgresResource(builder.Resource);
var azurePostgres = builder.ApplicationBuilder.CreateResourceBuilder(resource).ConfigureDefaults();
azurePostgres.WithLoginAndPassword(administratorLogin, administratorLoginPassword)
azurePostgres.WithLoginAndPassword(builder.Resource)
.WithParameter("databases", () => builder.Resource.Databases.Select(x => x.Value));

if (callback != null)
Expand All @@ -45,19 +41,15 @@ public static IResourceBuilder<PostgresServerResource> PublishAsAzurePostgresFle
/// Configures Postgres resource to be deployed as Azure Postgres Flexible Server when deployed using Azure Developer CLI and when the Azure Provisioner is used for local development.
/// </summary>
/// <param name="builder">The builder for the Postgres resource.</param>
/// <param name="administratorLogin">Parameter containing the administrator username for the server that will be provisioned in Azure.</param>
/// <param name="administratorLoginPassword">Parameter containing the administrator password for the server that will be provisioned in Azure.</param>
/// <param name="callback">Callback to customize the Azure resources that will be provisioned in Azure.</param>
/// <returns></returns>
public static IResourceBuilder<PostgresServerResource> AsAzurePostgresFlexibleServer(
this IResourceBuilder<PostgresServerResource> builder,
IResourceBuilder<ParameterResource>? administratorLogin = null,
IResourceBuilder<ParameterResource>? administratorLoginPassword = null,
Action<IResourceBuilder<AzurePostgresResource>>? callback = null)
{
var resource = new AzurePostgresResource(builder.Resource);
var azurePostgres = builder.ApplicationBuilder.CreateResourceBuilder(resource).ConfigureDefaults();
azurePostgres.WithLoginAndPassword(administratorLogin, administratorLoginPassword)
azurePostgres.WithLoginAndPassword(builder.Resource)
.WithParameter("databases", () => builder.Resource.Databases.Select(x => x.Value));

// Used to hold a reference to the azure surrogate for use with the provisioner.
Expand Down Expand Up @@ -86,12 +78,9 @@ private static IResourceBuilder<AzurePostgresResource> ConfigureDefaults(this IR
.WithParameter(AzureBicepResource.KnownParameters.KeyVaultName);
}

private static IResourceBuilder<T> WithLoginAndPassword<T>(
this IResourceBuilder<T> builder,
IResourceBuilder<ParameterResource>? administratorLogin,
IResourceBuilder<ParameterResource>? administratorLoginPassword) where T: AzureBicepResource
private static IResourceBuilder<T> WithLoginAndPassword<T>(this IResourceBuilder<T> builder, PostgresServerResource postgresResource) where T : AzureBicepResource
{
if (administratorLogin is null)
if (postgresResource.UserNameParameter is null)
{
const string usernameInput = "username";
// generate a username since a parameter was not provided
Expand All @@ -110,26 +99,24 @@ private static IResourceBuilder<T> WithLoginAndPassword<T>(
}
else
{
builder.WithParameter("administratorLogin", administratorLogin);
builder.WithParameter("administratorLogin", postgresResource.UserNameParameter);
}

if (administratorLoginPassword is null)
if (postgresResource.PasswordParameter is null)
{
// generate a password since a parameter was not provided. Use the existing "password" input from the underlying PostgresServerResource
builder.WithParameter("administratorLoginPassword", new InputReference(builder.Resource, "password"));
}
else
{
builder.WithParameter("administratorLoginPassword", administratorLoginPassword);
builder.WithParameter("administratorLoginPassword", postgresResource.PasswordParameter);
}

return builder;
}

internal static IResourceBuilder<PostgresServerResource> PublishAsAzurePostgresFlexibleServerConstruct(
this IResourceBuilder<PostgresServerResource> builder,
IResourceBuilder<ParameterResource>? administratorLogin = null,
IResourceBuilder<ParameterResource>? administratorLoginPassword = null,
Action<IResourceBuilder<AzurePostgresConstructResource>, ResourceModuleConstruct, PostgreSqlFlexibleServer>? configureResource = null,
bool useProvisioner = false)
{
Expand Down Expand Up @@ -183,7 +170,7 @@ internal static IResourceBuilder<PostgresServerResource> PublishAsAzurePostgresF
.WithParameter(AzureBicepResource.KnownParameters.PrincipalId)
.WithParameter(AzureBicepResource.KnownParameters.KeyVaultName)
.WithManifestPublishingCallback(resource.WriteToManifest)
.WithLoginAndPassword(administratorLogin, administratorLoginPassword);
.WithLoginAndPassword(builder.Resource);

if (builder.ApplicationBuilder.ExecutionContext.IsRunMode)
{
Expand All @@ -210,19 +197,13 @@ internal static IResourceBuilder<PostgresServerResource> PublishAsAzurePostgresF
/// Configures Postgres Server resource to be deployed as Azure Postgres Flexible Server.
/// </summary>
/// <param name="builder">The <see cref="IResourceBuilder{PostgresServerResource}"/> builder.</param>
/// <param name="administratorLogin"></param>
/// <param name="administratorLoginPassword"></param>
/// <param name="configureResource">Callback to configure Azure resource.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{PostgresServerResource}"/> builder.</returns>
public static IResourceBuilder<PostgresServerResource> PublishAsAzurePostgresFlexibleServerConstruct(
this IResourceBuilder<PostgresServerResource> builder,
IResourceBuilder<ParameterResource>? administratorLogin = null,
IResourceBuilder<ParameterResource>? administratorLoginPassword = null,
Action<IResourceBuilder<AzurePostgresConstructResource>, ResourceModuleConstruct, PostgreSqlFlexibleServer>? configureResource = null)
{
return builder.PublishAsAzurePostgresFlexibleServerConstruct(
administratorLogin,
administratorLoginPassword,
configureResource,
useProvisioner: false);
}
Expand All @@ -231,19 +212,13 @@ public static IResourceBuilder<PostgresServerResource> PublishAsAzurePostgresFle
/// Configures resource to use Azure for local development and when doing a deployment via the Azure Developer CLI.
/// </summary>
/// <param name="builder">The <see cref="IResourceBuilder{PostgresServerResource}"/> builder.</param>
/// <param name="administratorLogin"></param>
/// <param name="administratorLoginPassword"></param>
/// <param name="configureResource">Callback to configure Azure resource.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{PostgresServerResource}"/> builder.</returns>
public static IResourceBuilder<PostgresServerResource> AsAzurePostgresFlexibleServerConstruct(
this IResourceBuilder<PostgresServerResource> builder,
IResourceBuilder<ParameterResource>? administratorLogin = null,
IResourceBuilder<ParameterResource>? administratorLoginPassword = null,
Action<IResourceBuilder<AzurePostgresConstructResource>, ResourceModuleConstruct, PostgreSqlFlexibleServer>? configureResource = null)
{
return builder.PublishAsAzurePostgresFlexibleServerConstruct(
administratorLogin,
administratorLoginPassword,
configureResource,
useProvisioner: true);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Hosting/ApplicationModel/IValueProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
namespace Aspire.Hosting.ApplicationModel;

/// <summary>
/// A interface that allows the value to be provided for an environment variable.
/// An interface that allows the value to be provided for an environment variable.
/// </summary>
public interface IValueProvider
{
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Hosting/ApplicationModel/InputAnnotation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ private string GenerateValue()
return Default.GenerateDefaultValue();
}

internal static InputAnnotation CreateDefaultPasswordInput(string? password,
internal static InputAnnotation CreateDefaultPasswordInput(string? password = null,
bool lower = true, bool upper = true, bool numeric = true, bool special = true,
int minLower = 0, int minUpper = 0, int minNumeric = 0, int minSpecial = 0)
{
Expand Down
15 changes: 11 additions & 4 deletions src/Aspire.Hosting/Postgres/PostgresBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,34 @@ namespace Aspire.Hosting;
/// </summary>
public static class PostgresBuilderExtensions
{
private const string UserEnvVarName = "POSTGRES_USER";
private const string PasswordEnvVarName = "POSTGRES_PASSWORD";

/// <summary>
/// Adds a PostgreSQL resource to the application model. A container is used for local development. This version the package defaults to the 16.2 tag of the postgres container image
/// </summary>
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
/// <param name="userName">The parameter used to provide the user name for the PostgreSQL resource.</param>
/// <param name="password">The parameter used to provide the administrator password for the PostgreSQL resource. If null a random password will be generated.</param>
/// <param name="port">The host port used when launching the container. If null a random port will be assigned.</param>
/// <param name="password">The administrator password used for the container during local development. If null a random password will be generated.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<PostgresServerResource> AddPostgres(this IDistributedApplicationBuilder builder, string name, int? port = null, string? password = null)
public static IResourceBuilder<PostgresServerResource> AddPostgres(this IDistributedApplicationBuilder builder,
string name,
IResourceBuilder<ParameterResource>? userName = null,
IResourceBuilder<ParameterResource>? password = null,
int? port = null)
{
var postgresServer = new PostgresServerResource(name, password);
var postgresServer = new PostgresServerResource(name, userName?.Resource, password?.Resource);
return builder.AddResource(postgresServer)
.WithEndpoint(hostPort: port, containerPort: 5432, name: PostgresServerResource.PrimaryEndpointName) // Internal port is always 5432.
.WithImage("postgres", "16.2")
.WithEnvironment("POSTGRES_HOST_AUTH_METHOD", "scram-sha-256")
.WithEnvironment("POSTGRES_INITDB_ARGS", "--auth-host=scram-sha-256 --auth-local=scram-sha-256")
.WithEnvironment(context =>
{
context.EnvironmentVariables[PasswordEnvVarName] = postgresServer.PasswordInput;
context.EnvironmentVariables[UserEnvVarName] = postgresServer.UserNameReference;
context.EnvironmentVariables[PasswordEnvVarName] = postgresServer.PasswordReference;
});
}

Expand Down
37 changes: 31 additions & 6 deletions src/Aspire.Hosting/Postgres/PostgresServerResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,60 @@ namespace Aspire.Hosting.ApplicationModel;
public class PostgresServerResource : ContainerResource, IResourceWithConnectionString
{
internal const string PrimaryEndpointName = "tcp";
private const string DefaultUserName = "postgres";

/// <summary>
/// Initializes a new instance of the <see cref="PostgresServerResource"/> class.
/// </summary>
/// <param name="name">The name of the resource.</param>
/// <param name="password">The PostgreSQL server password, or <see langword="null"/> to generate a random password.</param>
public PostgresServerResource(string name, string? password = null) : base(name)
/// <param name="userName">A parameter that contains the PostgreSQL server user name.</param>
/// <param name="password">A parameter that contains the PostgreSQL server password.</param>
public PostgresServerResource(string name, ParameterResource? userName, ParameterResource? password) : base(name)
{
PrimaryEndpoint = new(this, PrimaryEndpointName);
PasswordInput = new(this, "password");

Annotations.Add(InputAnnotation.CreateDefaultPasswordInput(password));
UserNameParameter = userName;
PasswordParameter = password;

Annotations.Add(InputAnnotation.CreateDefaultPasswordInput());
}

/// <summary>
/// Gets the primary endpoint for the Redis server.
/// </summary>
public EndpointReference PrimaryEndpoint { get; }

internal InputReference PasswordInput { get; }
/// <summary>
/// Gets the parameter that contains the PostgreSQL server user name.
/// </summary>
public ParameterResource? UserNameParameter { get; }

internal ReferenceExpression UserNameReference =>
UserNameParameter is not null ?
ReferenceExpression.Create($"{UserNameParameter}") :
ReferenceExpression.Create($"{DefaultUserName}");

private InputReference PasswordInput { get; }

/// <summary>
/// Gets the parameter that contains the PostgreSQL server password.
/// </summary>
public ParameterResource? PasswordParameter { get; }

internal ReferenceExpression PasswordReference =>
PasswordParameter is not null ?
ReferenceExpression.Create($"{PasswordParameter}") :
ReferenceExpression.Create($"{PasswordInput}");

/// <summary>
/// Gets the PostgreSQL server password.
/// </summary>
public string Password => PasswordInput.Input.Value ?? throw new InvalidOperationException("Password cannot be null.");
internal string Password => PasswordParameter?.Value ?? PasswordInput.Input.Value ?? throw new InvalidOperationException("Password cannot be null.");

private ReferenceExpression ConnectionString =>
ReferenceExpression.Create(
$"Host={PrimaryEndpoint.Property(EndpointProperty.Host)};Port={PrimaryEndpoint.Property(EndpointProperty.Port)};Username=postgres;Password={PasswordInput}");
$"Host={PrimaryEndpoint.Property(EndpointProperty.Host)};Port={PrimaryEndpoint.Property(EndpointProperty.Port)};Username={UserNameReference};Password={PasswordReference}");

/// <summary>
/// Gets the connection string expression for the PostgreSQL server for the manifest.
Expand Down
Loading

0 comments on commit c2ace64

Please sign in to comment.