Skip to content

Commit 9e7c91d

Browse files
Don't set properties on existing Azure SQL server resources (#7705)
* Don't set properties on existing Azure SQL server resources * Set admin for existing resources correctly * Add workaround reference for Azure SDK issue * PR feedback. Add a link to Azure.Provisioning issue. * Remove `principalType` parameter from test assertion --------- Co-authored-by: Eric Erhardt <[email protected]>
1 parent a220851 commit 9e7c91d

File tree

2 files changed

+124
-41
lines changed

2 files changed

+124
-41
lines changed

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

Lines changed: 100 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Aspire.Hosting.Azure;
66
using Azure.Provisioning;
77
using Azure.Provisioning.Expressions;
8+
using Azure.Provisioning.Primitives;
89
using Azure.Provisioning.Sql;
910

1011
namespace Aspire.Hosting;
@@ -205,14 +206,6 @@ private static void CreateSqlServer(
205206
{
206207
var resource = SqlServer.FromExisting(identifier);
207208
resource.Name = name;
208-
resource.Administrators = new ServerExternalAdministrator()
209-
{
210-
AdministratorType = SqlAdministratorType.ActiveDirectory,
211-
IsAzureADOnlyAuthenticationEnabled = true,
212-
Sid = principalIdParameter,
213-
Login = principalNameParameter,
214-
TenantId = BicepFunction.GetSubscription().TenantId
215-
};
216209
return resource;
217210
},
218211
(infrastructure) =>
@@ -234,6 +227,20 @@ private static void CreateSqlServer(
234227
};
235228
});
236229

230+
// If the resource is an existing resource, we model the administrator access
231+
// for the managed identity as an "edge" between the parent SqlServer resource
232+
// and a custom SqlServerAzureADAdministrator resource.
233+
if (sqlServer.IsExistingResource)
234+
{
235+
var admin = new SqlServerAzureADAdministratorWorkaround($"{sqlServer.BicepIdentifier}_admin")
236+
{
237+
ParentOverride = sqlServer,
238+
LoginOverride = principalNameParameter,
239+
SidOverride = principalIdParameter
240+
};
241+
infrastructure.Add(admin);
242+
}
243+
237244
infrastructure.Add(new SqlFirewallRule("sqlFirewallRule_AllowAllAzureIps")
238245
{
239246
Parent = sqlServer,
@@ -244,11 +251,15 @@ private static void CreateSqlServer(
244251

245252
if (distributedApplicationBuilder.ExecutionContext.IsRunMode)
246253
{
247-
// When in run mode we inject the users identity and we need to specify
248-
// the principalType.
249-
var principalTypeParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalType, typeof(string));
250-
infrastructure.Add(principalTypeParameter);
251-
sqlServer.Administrators.PrincipalType = principalTypeParameter;
254+
// Avoid mutating properties on existing resources.
255+
if (!sqlServer.IsExistingResource)
256+
{
257+
// When in run mode we inject the users identity and we need to specify
258+
// the principalType.
259+
var principalTypeParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalType, typeof(string));
260+
infrastructure.Add(principalTypeParameter);
261+
sqlServer.Administrators.PrincipalType = principalTypeParameter;
262+
}
252263

253264
infrastructure.Add(new SqlFirewallRule("sqlFirewallRule_AllowAllIps")
254265
{
@@ -273,4 +284,80 @@ private static void CreateSqlServer(
273284

274285
infrastructure.Add(new ProvisioningOutput("sqlServerFqdn", typeof(string)) { Value = sqlServer.FullyQualifiedDomainName });
275286
}
287+
288+
/// <remarks>
289+
/// Workaround for issue using SqlServerAzureADAdministrator.
290+
/// See https://github.com/Azure/azure-sdk-for-net/issues/48364 for more information.
291+
/// </remarks>
292+
private sealed class SqlServerAzureADAdministratorWorkaround(string bicepIdentifier) : SqlServerAzureADAdministrator(bicepIdentifier)
293+
{
294+
private BicepValue<string>? _name;
295+
private BicepValue<string>? _login;
296+
private BicepValue<Guid>? _sid;
297+
private ResourceReference<SqlServer>? _parent;
298+
299+
/// <summary>
300+
/// Login name of the server administrator.
301+
/// </summary>
302+
public BicepValue<string> LoginOverride
303+
{
304+
get
305+
{
306+
Initialize();
307+
return _login!;
308+
}
309+
set
310+
{
311+
Initialize();
312+
_login!.Assign(value);
313+
}
314+
}
315+
316+
/// <summary>
317+
/// SID (object ID) of the server administrator.
318+
/// </summary>
319+
public BicepValue<Guid> SidOverride
320+
{
321+
get
322+
{
323+
Initialize();
324+
return _sid!;
325+
}
326+
set
327+
{
328+
Initialize();
329+
_sid!.Assign(value);
330+
}
331+
}
332+
333+
/// <summary>
334+
/// Parent resource of the server administrator.
335+
/// </summary>
336+
public SqlServer? ParentOverride
337+
{
338+
get
339+
{
340+
Initialize();
341+
return _parent!.Value;
342+
}
343+
set
344+
{
345+
Initialize();
346+
_parent!.Value = value;
347+
}
348+
}
349+
350+
private static BicepValue<string> GetNameDefaultValue()
351+
{
352+
return new StringLiteralExpression("ActiveDirectory");
353+
}
354+
355+
protected override void DefineProvisionableProperties()
356+
{
357+
_name = DefineProperty("Name", ["name"], defaultValue: GetNameDefaultValue());
358+
_login = DefineProperty<string>("Login", ["properties", "login"]);
359+
_sid = DefineProperty<Guid>("Sid", ["properties", "sid"]);
360+
_parent = DefineResource<SqlServer>("Parent", ["parent"], isOutput: false, isRequired: true);
361+
}
362+
}
276363
}

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

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1122,15 +1122,15 @@ param existingResourceName string
11221122
11231123
resource sqlServer 'Microsoft.Sql/servers@2021-11-01' existing = {
11241124
name: existingResourceName
1125+
}
1126+
1127+
resource sqlServer_admin 'Microsoft.Sql/servers/administrators@2021-11-01' = {
1128+
name: 'ActiveDirectory'
11251129
properties: {
1126-
administrators: {
1127-
administratorType: 'ActiveDirectory'
1128-
login: principalName
1129-
sid: principalId
1130-
tenantId: subscription().tenantId
1131-
azureADOnlyAuthentication: true
1132-
}
1130+
login: principalName
1131+
sid: principalId
11331132
}
1133+
parent: sqlServer
11341134
}
11351135
11361136
resource sqlFirewallRule_AllowAllAzureIps 'Microsoft.Sql/servers/firewallRules@2021-11-01' = {
@@ -1168,8 +1168,7 @@ public async Task SupportsExistingAzureSqlServerInRunMode()
11681168
"params": {
11691169
"existingResourceName": "{existingResourceName.value}",
11701170
"principalId": "",
1171-
"principalName": "",
1172-
"principalType": ""
1171+
"principalName": ""
11731172
}
11741173
}
11751174
""";
@@ -1186,20 +1185,17 @@ param principalName string
11861185
11871186
param existingResourceName string
11881187
1189-
param principalType string
1190-
11911188
resource sqlServer 'Microsoft.Sql/servers@2021-11-01' existing = {
11921189
name: existingResourceName
1190+
}
1191+
1192+
resource sqlServer_admin 'Microsoft.Sql/servers/administrators@2021-11-01' = {
1193+
name: 'ActiveDirectory'
11931194
properties: {
1194-
administrators: {
1195-
administratorType: 'ActiveDirectory'
1196-
principalType: principalType
1197-
login: principalName
1198-
sid: principalId
1199-
tenantId: subscription().tenantId
1200-
azureADOnlyAuthentication: true
1201-
}
1195+
login: principalName
1196+
sid: principalId
12021197
}
1198+
parent: sqlServer
12031199
}
12041200
12051201
resource sqlFirewallRule_AllowAllAzureIps 'Microsoft.Sql/servers/firewallRules@2021-11-01' = {
@@ -1372,13 +1368,13 @@ public async Task SupportsExistingAzureApplicationInsightsWithResourceGroup()
13721368
var expectedBicep = """
13731369
@description('The location for the resource(s) to be deployed.')
13741370
param location string = resourceGroup().location
1375-
1371+
13761372
param existingResourceName string
1377-
1373+
13781374
resource appInsights 'Microsoft.Insights/components@2020-02-02' existing = {
13791375
name: existingResourceName
13801376
}
1381-
1377+
13821378
output appInsightsConnectionString string = appInsights.properties.ConnectionString
13831379
""";
13841380

@@ -1419,17 +1415,17 @@ public async Task SupportsExistingAzureOpenAIWithResourceGroup()
14191415
var expectedBicep = """
14201416
@description('The location for the resource(s) to be deployed.')
14211417
param location string = resourceGroup().location
1422-
1418+
14231419
param existingResourceName string
1424-
1420+
14251421
param principalType string
1426-
1422+
14271423
param principalId string
1428-
1424+
14291425
resource openAI 'Microsoft.CognitiveServices/accounts@2024-10-01' existing = {
14301426
name: existingResourceName
14311427
}
1432-
1428+
14331429
resource openAI_CognitiveServicesOpenAIContributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
14341430
name: guid(openAI.id, principalId, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a001fd3d-188f-4b5d-821b-7da978bf7442'))
14351431
properties: {
@@ -1439,7 +1435,7 @@ param principalId string
14391435
}
14401436
scope: openAI
14411437
}
1442-
1438+
14431439
resource mymodel 'Microsoft.CognitiveServices/accounts/deployments@2024-10-01' = {
14441440
name: 'mymodel'
14451441
properties: {

0 commit comments

Comments
 (0)