|
// 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;
using Aspire.Hosting.Azure;
using Azure.Provisioning;
using Azure.Provisioning.Expressions;
using Azure.Provisioning.Sql;
namespace Aspire.Hosting;
/// <summary>
/// Provides extension methods for adding the Azure SQL resources to the application model.
/// </summary>
public static class AzureSqlExtensions
{
[Obsolete]
private static IResourceBuilder<SqlServerServerResource> PublishAsAzureSqlDatabase(this IResourceBuilder<SqlServerServerResource> builder, bool useProvisioner)
{
builder.ApplicationBuilder.AddAzureProvisioning();
var configureInfrastructure = (AzureResourceInfrastructure infrastructure) =>
{
CreateSqlServer(infrastructure, builder.ApplicationBuilder, builder.Resource.Databases);
};
var resource = new AzureSqlServerResource(builder.Resource, configureInfrastructure);
var azureSqlDatabase = builder.ApplicationBuilder.CreateResourceBuilder(resource);
azureSqlDatabase.WithManifestPublishingCallback(resource.WriteToManifest);
if (useProvisioner)
{
// Used to hold a reference to the azure surrogate for use with the provisioner.
builder.WithAnnotation(new AzureBicepResourceAnnotation(resource));
builder.WithConnectionStringRedirection(resource);
// Remove the container annotation so that DCP doesn't do anything with it.
if (builder.Resource.Annotations.OfType<ContainerImageAnnotation>().SingleOrDefault() is { } containerAnnotation)
{
builder.Resource.Annotations.Remove(containerAnnotation);
}
}
return builder;
}
/// <summary>
/// Configures SQL Server resource to be deployed as Azure SQL Database (server).
/// </summary>
/// <param name="builder">The builder for the SQL Server resource.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
[Obsolete($"This method is obsolete and will be removed in a future version. Use {nameof(AddAzureSqlServer)} instead to add an Azure SQL server resource.")]
public static IResourceBuilder<SqlServerServerResource> PublishAsAzureSqlDatabase(this IResourceBuilder<SqlServerServerResource> builder)
{
return builder.PublishAsAzureSqlDatabase(useProvisioner: false);
}
/// <summary>
/// Configures SQL Server resource to be deployed as Azure SQL Database (server).
/// </summary>
/// <param name="builder">The builder for the SQL Server resource.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
[Obsolete($"This method is obsolete and will be removed in a future version. Use {nameof(AddAzureSqlServer)} instead to add an Azure SQL server resource.")]
public static IResourceBuilder<SqlServerServerResource> AsAzureSqlDatabase(this IResourceBuilder<SqlServerServerResource> builder)
{
return builder.PublishAsAzureSqlDatabase(useProvisioner: true);
}
/// <summary>
/// Adds an Azure SQL Database (server) resource to the application model.
/// </summary>
/// <param name="builder">The builder for the distributed application.</param>
/// <param name="name">The name of the resource.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{AzureSqlServerResource}"/> builder.</returns>
public static IResourceBuilder<AzureSqlServerResource> AddAzureSqlServer(this IDistributedApplicationBuilder builder, [ResourceName] string name)
{
builder.AddAzureProvisioning();
var configureInfrastructure = (AzureResourceInfrastructure infrastructure) =>
{
var azureResource = (AzureSqlServerResource)infrastructure.AspireResource;
CreateSqlServer(infrastructure, builder, azureResource.Databases);
};
var resource = new AzureSqlServerResource(name, configureInfrastructure);
var azureSqlServer = builder.AddResource(resource)
.WithManifestPublishingCallback(resource.WriteToManifest);
return azureSqlServer;
}
/// <summary>
/// Adds an Azure SQL Database to the application model.
/// </summary>
/// <param name="builder">The builder for the Azure SQL resource.</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="databaseName">The name of the database. If not provided, this defaults to the same value as <paramref name="name"/>.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<AzureSqlDatabaseResource> AddDatabase(this IResourceBuilder<AzureSqlServerResource> builder, [ResourceName] string name, string? databaseName = null)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(name);
// Use the resource name as the database name if it's not provided
databaseName ??= name;
var azureResource = builder.Resource;
var azureSqlDatabase = new AzureSqlDatabaseResource(name, databaseName, azureResource);
builder.Resource.AddDatabase(name, databaseName);
if (azureResource.InnerResource is null)
{
return builder.ApplicationBuilder.AddResource(azureSqlDatabase);
}
else
{
// need to add the database to the InnerResource
var innerBuilder = builder.ApplicationBuilder.CreateResourceBuilder(azureResource.InnerResource);
var innerDb = innerBuilder.AddDatabase(name, databaseName);
azureSqlDatabase.SetInnerResource(innerDb.Resource);
// create a builder, but don't add the Azure database to the model because the InnerResource already has it
return builder.ApplicationBuilder.CreateResourceBuilder(azureSqlDatabase);
}
}
/// <summary>
/// Configures an Azure SQL Database (server) resource to run locally in a container.
/// </summary>
/// <param name="builder">The builder for the Azure SQL resource.</param>
/// <param name="configureContainer">Callback that exposes underlying container to allow for customization.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{AzureSqlServerResource}"/> builder.</returns>
/// <example>
/// 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.
/// <code lang="csharp">
/// var builder = DistributedApplication.CreateBuilder(args);
///
/// var data = builder.AddAzureSqlServer("data")
/// .RunAsContainer();
///
/// builder.AddProject<Projects.ProductService>()
/// .WithReference(data);
///
/// builder.Build().Run();
/// </code>
/// </example>
public static IResourceBuilder<AzureSqlServerResource> RunAsContainer(this IResourceBuilder<AzureSqlServerResource> builder, Action<IResourceBuilder<SqlServerServerResource>>? configureContainer = null)
{
if (builder.ApplicationBuilder.ExecutionContext.IsPublishMode)
{
return builder;
}
var azureResource = builder.Resource;
var azureDatabases = builder.ApplicationBuilder.Resources
.OfType<AzureSqlDatabaseResource>()
.Where(db => db.Parent == azureResource)
.ToDictionary(db => db.Name);
RemoveAzureResources(builder.ApplicationBuilder, azureResource, azureDatabases);
var sqlContainer = builder.ApplicationBuilder.AddSqlServer(azureResource.Name);
azureResource.SetInnerResource(sqlContainer.Resource);
foreach (var database in azureResource.Databases)
{
if (!azureDatabases.TryGetValue(database.Key, out var existingDb))
{
throw new InvalidOperationException($"Could not find a {nameof(AzureSqlDatabaseResource)} with name {database.Key}.");
}
var innerDb = sqlContainer.AddDatabase(database.Key, database.Value);
existingDb.SetInnerResource(innerDb.Resource);
}
configureContainer?.Invoke(sqlContainer);
return builder;
}
private static void RemoveAzureResources(IDistributedApplicationBuilder appBuilder, AzureSqlServerResource azureResource, Dictionary<string, AzureSqlDatabaseResource> azureDatabases)
{
appBuilder.Resources.Remove(azureResource);
foreach (var database in azureDatabases)
{
appBuilder.Resources.Remove(database.Value);
}
}
private static void CreateSqlServer(
AzureResourceInfrastructure infrastructure,
IDistributedApplicationBuilder distributedApplicationBuilder,
IReadOnlyDictionary<string, string> databases)
{
var principalIdParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalId, typeof(string));
infrastructure.Add(principalIdParameter);
var principalNameParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalName, typeof(string));
infrastructure.Add(principalNameParameter);
var sqlServer = new SqlServer(infrastructure.AspireResource.GetBicepIdentifier())
{
Administrators = new ServerExternalAdministrator()
{
AdministratorType = SqlAdministratorType.ActiveDirectory,
IsAzureADOnlyAuthenticationEnabled = true,
Sid = principalIdParameter,
Login = principalNameParameter,
TenantId = BicepFunction.GetSubscription().TenantId
},
Version = "12.0",
PublicNetworkAccess = ServerNetworkAccessFlag.Enabled,
MinTlsVersion = SqlMinimalTlsVersion.Tls1_2,
Tags = { { "aspire-resource-name", infrastructure.AspireResource.Name } }
};
infrastructure.Add(sqlServer);
infrastructure.Add(new SqlFirewallRule("sqlFirewallRule_AllowAllAzureIps")
{
Parent = sqlServer,
Name = "AllowAllAzureIps",
StartIPAddress = "0.0.0.0",
EndIPAddress = "0.0.0.0"
});
if (distributedApplicationBuilder.ExecutionContext.IsRunMode)
{
// When in run mode we inject the users identity and we need to specify
// the principalType.
var principalTypeParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalType, typeof(string));
infrastructure.Add(principalTypeParameter);
sqlServer.Administrators.PrincipalType = principalTypeParameter;
infrastructure.Add(new SqlFirewallRule("sqlFirewallRule_AllowAllIps")
{
Parent = sqlServer,
Name = "AllowAllIps",
StartIPAddress = "0.0.0.0",
EndIPAddress = "255.255.255.255"
});
}
foreach (var databaseNames in databases)
{
var bicepIdentifier = Infrastructure.NormalizeBicepIdentifier(databaseNames.Key);
var databaseName = databaseNames.Value;
var sqlDatabase = new SqlDatabase(bicepIdentifier)
{
Parent = sqlServer,
Name = databaseName
};
infrastructure.Add(sqlDatabase);
}
infrastructure.Add(new ProvisioningOutput("sqlServerFqdn", typeof(string)) { Value = sqlServer.FullyQualifiedDomainName });
}
}
|