Skip to content

Commit 460fa56

Browse files
authored
Default to the free Azure SqlServer DB SKU (#8887)
1 parent a248f5e commit 460fa56

File tree

7 files changed

+169
-28
lines changed

7 files changed

+169
-28
lines changed

src/Aspire.Hosting.Azure.Sql/AzureSqlDatabaseResource.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ namespace Aspire.Hosting.Azure;
1616
public class AzureSqlDatabaseResource(string name, string databaseName, AzureSqlServerResource parent)
1717
: Resource(name), IResourceWithParent<AzureSqlServerResource>, IResourceWithConnectionString
1818
{
19+
/// <summary>
20+
/// SKU associated with the free offer
21+
/// </summary>
22+
internal const string FREE_DB_SKU = "GP_S_Gen5_2";
23+
1924
/// <summary>
2025
/// Gets the parent Azure SQL Database (server) resource.
2126
/// </summary>
@@ -32,6 +37,11 @@ public class AzureSqlDatabaseResource(string name, string databaseName, AzureSql
3237
/// </summary>
3338
public string DatabaseName { get; } = ThrowIfNullOrEmpty(databaseName);
3439

40+
/// <summary>
41+
/// Gets or Sets the database SKU name
42+
/// </summary>
43+
internal bool UseDefaultAzureSku { get; set; } // Default to false
44+
3545
/// <summary>
3646
/// Gets the inner SqlServerDatabaseResource resource.
3747
///
@@ -58,5 +68,4 @@ internal void SetInnerResource(SqlServerDatabaseResource innerResource)
5868

5969
InnerResource = innerResource;
6070
}
61-
6271
}

src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs

Lines changed: 68 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public static IResourceBuilder<AzureSqlServerResource> AddAzureSqlServer(this ID
8181
var configureInfrastructure = (AzureResourceInfrastructure infrastructure) =>
8282
{
8383
var azureResource = (AzureSqlServerResource)infrastructure.AspireResource;
84-
CreateSqlServer(infrastructure, builder, azureResource.Databases);
84+
CreateSqlServer(infrastructure, builder, azureResource.AzureSqlDatabases);
8585
};
8686

8787
var resource = new AzureSqlServerResource(name, configureInfrastructure);
@@ -93,6 +93,7 @@ public static IResourceBuilder<AzureSqlServerResource> AddAzureSqlServer(this ID
9393

9494
/// <summary>
9595
/// Adds an Azure SQL Database to the application model.
96+
/// The Free Offer option will be used when deploying the resource in Azure
9697
/// </summary>
9798
/// <param name="builder">The builder for the Azure SQL resource.</param>
9899
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
@@ -109,7 +110,7 @@ public static IResourceBuilder<AzureSqlDatabaseResource> AddDatabase(this IResou
109110
var azureResource = builder.Resource;
110111
var azureSqlDatabase = new AzureSqlDatabaseResource(name, databaseName, azureResource);
111112

112-
builder.Resource.AddDatabase(name, databaseName);
113+
builder.Resource.AddDatabase(azureSqlDatabase);
113114

114115
if (azureResource.InnerResource is null)
115116
{
@@ -127,6 +128,18 @@ public static IResourceBuilder<AzureSqlDatabaseResource> AddDatabase(this IResou
127128
}
128129
}
129130

131+
/// <summary>
132+
/// Configures the Azure SQL Database to be deployed use the default SKU provided by Azure.
133+
/// Please be aware that the Azure default Sku might not take advantage of the free offer.
134+
/// </summary>
135+
/// <param name="builder">The builder for the Azure SQL resource.</param>
136+
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
137+
public static IResourceBuilder<AzureSqlDatabaseResource> WithDefaultAzureSku(this IResourceBuilder<AzureSqlDatabaseResource> builder)
138+
{
139+
builder.Resource.UseDefaultAzureSku = true;
140+
return builder;
141+
}
142+
130143
/// <summary>
131144
/// Configures an Azure SQL Database (server) resource to run locally in a container.
132145
/// </summary>
@@ -171,14 +184,14 @@ public static IResourceBuilder<AzureSqlServerResource> RunAsContainer(this IReso
171184

172185
azureResource.SetInnerResource(sqlContainer.Resource);
173186

174-
foreach (var database in azureResource.Databases)
187+
foreach (var database in azureResource.AzureSqlDatabases)
175188
{
176189
if (!azureDatabases.TryGetValue(database.Key, out var existingDb))
177190
{
178191
throw new InvalidOperationException($"Could not find a {nameof(AzureSqlDatabaseResource)} with name {database.Key}.");
179192
}
180193

181-
var innerDb = sqlContainer.AddDatabase(database.Key, database.Value);
194+
var innerDb = sqlContainer.AddDatabase(database.Key, database.Value.DatabaseName);
182195
existingDb.SetInnerResource(innerDb.Resource);
183196
}
184197

@@ -200,6 +213,55 @@ private static void CreateSqlServer(
200213
AzureResourceInfrastructure infrastructure,
201214
IDistributedApplicationBuilder distributedApplicationBuilder,
202215
IReadOnlyDictionary<string, string> databases)
216+
{
217+
var sqlServer = CreateSqlServerResourceOnly(infrastructure, distributedApplicationBuilder);
218+
219+
foreach (var database in databases)
220+
{
221+
var sqlDatabase = CreateAzureSQLDatabase(sqlServer, database.Key, database.Value);
222+
223+
infrastructure.Add(sqlDatabase);
224+
}
225+
}
226+
227+
private static void CreateSqlServer(
228+
AzureResourceInfrastructure infrastructure,
229+
IDistributedApplicationBuilder distributedApplicationBuilder,
230+
IReadOnlyDictionary<string, AzureSqlDatabaseResource> databases)
231+
{
232+
var sqlServer = CreateSqlServerResourceOnly(infrastructure, distributedApplicationBuilder);
233+
234+
foreach (var database in databases)
235+
{
236+
var sqlDatabase = CreateAzureSQLDatabase(sqlServer, database.Key, database.Value.DatabaseName);
237+
238+
// Unless user specifically mention that they want to use the Azure defaults,
239+
// an Azure SQL DB using the free option will be created
240+
if (database.Value.UseDefaultAzureSku == false)
241+
{
242+
sqlDatabase.Sku = new SqlSku() { Name = AzureSqlDatabaseResource.FREE_DB_SKU };
243+
sqlDatabase.UseFreeLimit = true;
244+
sqlDatabase.FreeLimitExhaustionBehavior = FreeLimitExhaustionBehavior.AutoPause;
245+
}
246+
247+
infrastructure.Add(sqlDatabase);
248+
}
249+
}
250+
251+
private static SqlDatabase CreateAzureSQLDatabase(SqlServer sqlServer, string databaseKey, string databaseName)
252+
{
253+
var bicepIdentifier = Infrastructure.NormalizeBicepIdentifier(databaseKey);
254+
var sqlDatabase = new SqlDatabase(bicepIdentifier)
255+
{
256+
Parent = sqlServer,
257+
Name = databaseName,
258+
};
259+
260+
return sqlDatabase;
261+
}
262+
263+
private static SqlServer CreateSqlServerResourceOnly(AzureResourceInfrastructure infrastructure,
264+
IDistributedApplicationBuilder distributedApplicationBuilder)
203265
{
204266
var azureResource = (AzureSqlServerResource)infrastructure.AspireResource;
205267

@@ -265,22 +327,12 @@ private static void CreateSqlServer(
265327
});
266328
}
267329

268-
foreach (var databaseNames in databases)
269-
{
270-
var bicepIdentifier = Infrastructure.NormalizeBicepIdentifier(databaseNames.Key);
271-
var databaseName = databaseNames.Value;
272-
var sqlDatabase = new SqlDatabase(bicepIdentifier)
273-
{
274-
Parent = sqlServer,
275-
Name = databaseName
276-
};
277-
infrastructure.Add(sqlDatabase);
278-
}
279-
280330
infrastructure.Add(new ProvisioningOutput("sqlServerFqdn", typeof(string)) { Value = sqlServer.FullyQualifiedDomainName });
281331

282332
// We need to output name to externalize role assignments.
283333
infrastructure.Add(new ProvisioningOutput("name", typeof(string)) { Value = sqlServer.Name });
334+
335+
return sqlServer;
284336
}
285337

286338
internal static SqlServerAzureADAdministrator AddActiveDirectoryAdministrator(AzureResourceInfrastructure infra, SqlServer sqlServer, BicepValue<Guid> principalId, BicepValue<string> principalName)

src/Aspire.Hosting.Azure.Sql/AzureSqlServerResource.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace Aspire.Hosting.Azure;
1212
/// </summary>
1313
public class AzureSqlServerResource : AzureProvisioningResource, IResourceWithConnectionString
1414
{
15-
private readonly Dictionary<string, string> _databases = new Dictionary<string, string>(StringComparers.ResourceName);
15+
private readonly Dictionary<string, AzureSqlDatabaseResource> _databases = new Dictionary<string, AzureSqlDatabaseResource>(StringComparers.ResourceName);
1616
private readonly bool _createdWithInnerResource;
1717

1818
/// <summary>
@@ -75,13 +75,21 @@ public ReferenceExpression ConnectionStringExpression
7575
public override ResourceAnnotationCollection Annotations => InnerResource?.Annotations ?? base.Annotations;
7676

7777
/// <summary>
78-
/// A dictionary where the key is the resource name and the value is the database name.
78+
/// A dictionary where the key is the resource name and the value is the Azure SQL database resource.
7979
/// </summary>
80-
public IReadOnlyDictionary<string, string> Databases => _databases;
80+
public IReadOnlyDictionary<string, AzureSqlDatabaseResource> AzureSqlDatabases => _databases;
8181

82-
internal void AddDatabase(string name, string databaseName)
82+
/// <summary>
83+
/// A dictionary where the key is the resource name and the value is the Azure SQL database name.
84+
/// </summary>
85+
public IReadOnlyDictionary<string, string> Databases => _databases.ToDictionary(
86+
kvp => kvp.Key,
87+
kvp => kvp.Value.DatabaseName
88+
);
89+
90+
internal void AddDatabase(AzureSqlDatabaseResource db)
8391
{
84-
_databases.TryAdd(name, databaseName);
92+
_databases.TryAdd(db.Name, db);
8593
}
8694

8795
internal void SetInnerResource(SqlServerServerResource innerResource)

src/Aspire.Hosting.Azure.Sql/README.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Aspire.Hosting.Azure.Sql library
22

3-
Provides extension methods and resource definitions for a .NET Aspire AppHost to configure Azure SQL Server.
3+
Provides extension methods and resource definitions for a .NET Aspire AppHost to configure Azure SQL DB.
44

55
## Getting started
66

@@ -54,6 +54,37 @@ The `WithReference` method configures a connection in the `MyService` project na
5454
builder.AddSqlServerClient("sqldata");
5555
```
5656

57+
## Azure SQL DB defaults
58+
59+
Unless otherwise specified, the Azure SQL DB created will be a 2vCores General Purpose Serverless database (GP_S_Gen5_2) with the free offer enabled.
60+
61+
Read more about the free offer here: [Deploy Azure SQL Database for free](https://learn.microsoft.com/azure/azure-sql/database/free-offer?view=azuresql)
62+
63+
The free offer is configured so that when the maximum usage limit is reached, the database is stopped to avoid incurring in unexpected costs.
64+
65+
If you **don't want to use the free offer** and instead deploy the database use the default sku set by Azure, use the `WithAzureDefaultSku` method:
66+
67+
```csharp
68+
var sql = builder.AddAzureSqlServer("sql")
69+
.AddDatabase("db", "my-db-name")
70+
.WithAzureDefaultSku();
71+
```
72+
73+
## Setting a specific SKU
74+
75+
If you want to manually define what SKU must be used when deploying the Azure SQL DB resource, use the `ConfigureInfrastructure` method:
76+
77+
```csharp
78+
var sqlSrv = builder.AddAzureSqlServer("sqlsrv")
79+
.ConfigureInfrastructure(infra => {
80+
var azureResources = infra.GetProvisionableResources();
81+
var azureDb = azureResources.OfType<SqlDatabase>().Single();
82+
azureDb.Sku = new SqlSku() { Name = "HS_Gen5_2" };
83+
})
84+
.AddDatabase("sqldb", "DatabaseName");
85+
.RunAsContainer();
86+
```
87+
5788
## Additional documentation
5889

5990
* https://learn.microsoft.com/dotnet/framework/data/adonet/sql/

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1260,7 +1260,7 @@ public async Task AddContainerAppEnvironmentWorksWithSqlServer()
12601260
builder.AddAzureContainerAppEnvironment("env");
12611261

12621262
var sql = builder.AddAzureSqlServer("sql");
1263-
var db = sql.AddDatabase("db");
1263+
var db = sql.AddDatabase("db").WithDefaultAzureSku();
12641264

12651265
builder.AddContainer("cache", "redis")
12661266
.WithReference(db);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public async Task AzureResourceOptionsCanBeConfigured()
3434
// ensure that resources with a hyphen still have a hyphen in the bicep name
3535
var sqlDatabase = builder.AddAzureSqlServer("sql-server")
3636
.RunAsContainer(x => x.WithLifetime(ContainerLifetime.Persistent))
37-
.AddDatabase("evadexdb");
37+
.AddDatabase("evadexdb").WithDefaultAzureSku();
3838

3939
using var app = builder.Build();
4040
await app.StartAsync();

0 commit comments

Comments
 (0)