Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 89 additions & 115 deletions src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using Azure.Provisioning.Expressions;
using Azure.Provisioning.KeyVault;
using Azure.Provisioning.PostgreSql;
using Azure.Provisioning.Primitives;

namespace Aspire.Hosting;

Expand Down Expand Up @@ -142,44 +141,7 @@ public static IResourceBuilder<AzurePostgresFlexibleServerResource> AddAzurePost
{
builder.AddAzureProvisioning();

var configureInfrastructure = (AzureResourceInfrastructure infrastructure) =>
{
var azureResource = (AzurePostgresFlexibleServerResource)infrastructure.AspireResource;
var postgres = CreatePostgreSqlFlexibleServer(infrastructure, builder, azureResource.Databases);

postgres.AuthConfig = new PostgreSqlFlexibleServerAuthConfig()
{
ActiveDirectoryAuth = PostgreSqlFlexibleServerActiveDirectoryAuthEnum.Enabled,
PasswordAuth = PostgreSqlFlexibleServerPasswordAuthEnum.Disabled
};

var principalIdParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalId, typeof(string));
var principalTypeParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalType, typeof(string));
var principalNameParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalName, typeof(string));

var admin = new PostgreSqlFlexibleServerActiveDirectoryAdministrator($"{postgres.BicepIdentifier}_admin")
{
Parent = postgres,
Name = principalIdParameter,
PrincipalType = principalTypeParameter,
PrincipalName = principalNameParameter,
};

// This is a workaround for a bug in the API that requires the parent to be fully resolved
admin.DependsOn.Add(postgres);
foreach (var firewall in infrastructure.GetProvisionableResources().OfType<PostgreSqlFlexibleServerFirewallRule>())
{
admin.DependsOn.Add(firewall);
}
infrastructure.Add(admin);

infrastructure.Add(new ProvisioningOutput("connectionString", typeof(string))
{
Value = BicepFunction.Interpolate($"Host={postgres.FullyQualifiedDomainName};Username={principalNameParameter}")
});
};

var resource = new AzurePostgresFlexibleServerResource(name, configureInfrastructure);
var resource = new AzurePostgresFlexibleServerResource(name, infrastructure => ConfigurePostgreSqlInfrastructure(infrastructure, builder));
return builder.AddResource(resource)
.WithParameter(AzureBicepResource.KnownParameters.PrincipalId)
.WithParameter(AzureBicepResource.KnownParameters.PrincipalType)
Expand Down Expand Up @@ -348,63 +310,7 @@ public static IResourceBuilder<AzurePostgresFlexibleServerResource> WithPassword

return builder
.RemoveActiveDirectoryParameters()
.WithParameter(AzureBicepResource.KnownParameters.KeyVaultName)
.ConfigureInfrastructure(static infrastructure =>
{
var azureResource = (AzurePostgresFlexibleServerResource)infrastructure.AspireResource;

RemoveActiveDirectoryAuthResources(infrastructure);

var postgres = infrastructure.GetProvisionableResources().OfType<PostgreSqlFlexibleServer>().FirstOrDefault(r => r.BicepIdentifier == azureResource.GetBicepIdentifier())
?? throw new InvalidOperationException($"Could not find a PostgreSqlFlexibleServer with name {azureResource.Name}.");

var administratorLogin = new ProvisioningParameter("administratorLogin", typeof(string));
infrastructure.Add(administratorLogin);

var administratorLoginPassword = new ProvisioningParameter("administratorLoginPassword", typeof(string)) { IsSecure = true };
infrastructure.Add(administratorLoginPassword);

var kvNameParam = new ProvisioningParameter("keyVaultName", typeof(string));
infrastructure.Add(kvNameParam);

var keyVault = KeyVaultService.FromExisting("keyVault");
keyVault.Name = kvNameParam;
infrastructure.Add(keyVault);

postgres.AuthConfig = new PostgreSqlFlexibleServerAuthConfig()
{
ActiveDirectoryAuth = PostgreSqlFlexibleServerActiveDirectoryAuthEnum.Disabled,
PasswordAuth = PostgreSqlFlexibleServerPasswordAuthEnum.Enabled
};

postgres.AdministratorLogin = administratorLogin;
postgres.AdministratorLoginPassword = administratorLoginPassword;

var secret = new KeyVaultSecret("connectionString")
{
Parent = keyVault,
Name = "connectionString",
Properties = new SecretProperties
{
Value = BicepFunction.Interpolate($"Host={postgres.FullyQualifiedDomainName};Username={administratorLogin};Password={administratorLoginPassword}")
}
};
infrastructure.Add(secret);

foreach (var database in azureResource.Databases)
{
var dbSecret = new KeyVaultSecret(Infrastructure.NormalizeBicepIdentifier(database.Key + "_connectionString"))
{
Parent = keyVault,
Name = AzurePostgresFlexibleServerResource.GetDatabaseKeyVaultSecretName(database.Key),
Properties = new SecretProperties
{
Value = BicepFunction.Interpolate($"Host={postgres.FullyQualifiedDomainName};Username={administratorLogin};Password={administratorLoginPassword};Database={database.Value}")
}
};
infrastructure.Add(dbSecret);
}
});
.WithParameter(AzureBicepResource.KnownParameters.KeyVaultName);
}

private static PostgreSqlFlexibleServer CreatePostgreSqlFlexibleServer(AzureResourceInfrastructure infrastructure, IDistributedApplicationBuilder distributedApplicationBuilder, IReadOnlyDictionary<string, string> databases)
Expand Down Expand Up @@ -468,36 +374,104 @@ private static PostgreSqlFlexibleServer CreatePostgreSqlFlexibleServer(AzureReso
return postgres;
}

private static IResourceBuilder<AzurePostgresFlexibleServerResource> RemoveActiveDirectoryParameters(
this IResourceBuilder<AzurePostgresFlexibleServerResource> builder)
private static void ConfigurePostgreSqlInfrastructure(AzureResourceInfrastructure infrastructure, IDistributedApplicationBuilder distributedApplicationBuilder)
{
builder.Resource.Parameters.Remove(AzureBicepResource.KnownParameters.PrincipalId);
builder.Resource.Parameters.Remove(AzureBicepResource.KnownParameters.PrincipalType);
builder.Resource.Parameters.Remove(AzureBicepResource.KnownParameters.PrincipalName);
return builder;
}
var azureResource = (AzurePostgresFlexibleServerResource)infrastructure.AspireResource;
var postgres = CreatePostgreSqlFlexibleServer(infrastructure, distributedApplicationBuilder, azureResource.Databases);

private static void RemoveActiveDirectoryAuthResources(AzureResourceInfrastructure infrastructure)
{
var resourcesToRemove = new List<Provisionable>();
foreach (var resource in infrastructure.GetProvisionableResources())
if (azureResource.UsePasswordAuthentication)
{
if (resource is PostgreSqlFlexibleServerActiveDirectoryAdministrator)
var administratorLogin = new ProvisioningParameter("administratorLogin", typeof(string));
infrastructure.Add(administratorLogin);

var administratorLoginPassword = new ProvisioningParameter("administratorLoginPassword", typeof(string)) { IsSecure = true };
infrastructure.Add(administratorLoginPassword);

var kvNameParam = new ProvisioningParameter("keyVaultName", typeof(string));
infrastructure.Add(kvNameParam);

var keyVault = KeyVaultService.FromExisting("keyVault");
keyVault.Name = kvNameParam;
infrastructure.Add(keyVault);

postgres.AuthConfig = new PostgreSqlFlexibleServerAuthConfig()
{
resourcesToRemove.Add(resource);
}
else if (resource is ProvisioningOutput output && output.BicepIdentifier == "connectionString")
ActiveDirectoryAuth = PostgreSqlFlexibleServerActiveDirectoryAuthEnum.Disabled,
PasswordAuth = PostgreSqlFlexibleServerPasswordAuthEnum.Enabled
};

postgres.AdministratorLogin = administratorLogin;
postgres.AdministratorLoginPassword = administratorLoginPassword;

var secret = new KeyVaultSecret("connectionString")
{
Parent = keyVault,
Name = "connectionString",
Properties = new SecretProperties
{
Value = BicepFunction.Interpolate($"Host={postgres.FullyQualifiedDomainName};Username={administratorLogin};Password={administratorLoginPassword}")
}
};
infrastructure.Add(secret);

foreach (var database in azureResource.Databases)
{
resourcesToRemove.Add(resource);
var dbSecret = new KeyVaultSecret(Infrastructure.NormalizeBicepIdentifier(database.Key + "_connectionString"))
{
Parent = keyVault,
Name = AzurePostgresFlexibleServerResource.GetDatabaseKeyVaultSecretName(database.Key),
Properties = new SecretProperties
{
Value = BicepFunction.Interpolate($"Host={postgres.FullyQualifiedDomainName};Username={administratorLogin};Password={administratorLoginPassword};Database={database.Value}")
}
};
infrastructure.Add(dbSecret);
}
}

foreach (var resourceToRemove in resourcesToRemove)
else
{
infrastructure.Remove(resourceToRemove);
postgres.AuthConfig = new PostgreSqlFlexibleServerAuthConfig()
{
ActiveDirectoryAuth = PostgreSqlFlexibleServerActiveDirectoryAuthEnum.Enabled,
PasswordAuth = PostgreSqlFlexibleServerPasswordAuthEnum.Disabled
};

var principalIdParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalId, typeof(string));
var principalTypeParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalType, typeof(string));
var principalNameParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalName, typeof(string));

var admin = new PostgreSqlFlexibleServerActiveDirectoryAdministrator($"{postgres.BicepIdentifier}_admin")
{
Parent = postgres,
Name = principalIdParameter,
PrincipalType = principalTypeParameter,
PrincipalName = principalNameParameter,
};

// This is a workaround for a bug in the API that requires the parent to be fully resolved
admin.DependsOn.Add(postgres);
foreach (var firewall in infrastructure.GetProvisionableResources().OfType<PostgreSqlFlexibleServerFirewallRule>())
{
admin.DependsOn.Add(firewall);
}
infrastructure.Add(admin);

infrastructure.Add(new ProvisioningOutput("connectionString", typeof(string))
{
Value = BicepFunction.Interpolate($"Host={postgres.FullyQualifiedDomainName};Username={principalNameParameter}")
});
}
}

private static IResourceBuilder<AzurePostgresFlexibleServerResource> RemoveActiveDirectoryParameters(
this IResourceBuilder<AzurePostgresFlexibleServerResource> builder)
{
builder.Resource.Parameters.Remove(AzureBicepResource.KnownParameters.PrincipalId);
builder.Resource.Parameters.Remove(AzureBicepResource.KnownParameters.PrincipalType);
builder.Resource.Parameters.Remove(AzureBicepResource.KnownParameters.PrincipalName);
return builder;
}

private static ParameterResource CreateDefaultUserNameParameter<T>(IResourceBuilder<T> builder) where T : AzureBicepResource
{
var generatedUserName = new GenerateParameterDefault
Expand Down
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.Diagnostics.CodeAnalysis;
using Aspire.Hosting.ApplicationModel;

namespace Aspire.Hosting.Azure;
Expand Down Expand Up @@ -30,6 +31,9 @@ public class AzurePostgresFlexibleServerResource(string name, Action<AzureResour
/// </summary>
internal BicepSecretOutputReference? ConnectionStringSecretOutput { get; set; }

[MemberNotNullWhen(true, nameof(ConnectionStringSecretOutput))]
internal bool UsePasswordAuthentication => ConnectionStringSecretOutput is not null;

/// <summary>
/// Gets the inner PostgresServerResource resource.
///
Expand All @@ -55,8 +59,9 @@ public class AzurePostgresFlexibleServerResource(string name, Action<AzureResour
/// </summary>
public ReferenceExpression ConnectionStringExpression =>
InnerResource?.ConnectionStringExpression ??
(ConnectionStringSecretOutput is not null ? ReferenceExpression.Create($"{ConnectionStringSecretOutput}") :
ReferenceExpression.Create($"{ConnectionStringOutput}"));
(UsePasswordAuthentication ?
ReferenceExpression.Create($"{ConnectionStringSecretOutput}") :
ReferenceExpression.Create($"{ConnectionStringOutput}"));

/// <summary>
/// A dictionary where the key is the resource name and the value is the database name.
Expand Down
6 changes: 5 additions & 1 deletion src/Aspire.Hosting.Azure.Redis/AzureRedisCacheResource.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.Diagnostics.CodeAnalysis;
using Aspire.Hosting.ApplicationModel;

namespace Aspire.Hosting.Azure;
Expand Down Expand Up @@ -28,6 +29,9 @@ public class AzureRedisCacheResource(string name, Action<AzureResourceInfrastruc
/// </summary>
internal BicepSecretOutputReference? ConnectionStringSecretOutput { get; set; }

[MemberNotNullWhen(true, nameof(ConnectionStringSecretOutput))]
internal bool UseAccessKeyAuthentication => ConnectionStringSecretOutput is not null;

/// <summary>
/// Gets the inner Redis resource.
///
Expand All @@ -43,7 +47,7 @@ public class AzureRedisCacheResource(string name, Action<AzureResourceInfrastruc
/// </summary>
public ReferenceExpression ConnectionStringExpression =>
InnerResource?.ConnectionStringExpression ??
(ConnectionStringSecretOutput is not null ?
(UseAccessKeyAuthentication ?
ReferenceExpression.Create($"{ConnectionStringSecretOutput}") :
ReferenceExpression.Create($"{ConnectionStringOutput}"));

Expand Down
Loading