Skip to content

Commit 5bab8b4

Browse files
github-actions[bot]captainsafiaeerhardt
authored
[release/9.1] Don't set properties on existing Azure SQL server resources (#7707)
* 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: Safia Abdalla <[email protected]> Co-authored-by: Safia Abdalla <[email protected]> Co-authored-by: Eric Erhardt <[email protected]>
1 parent a7453c1 commit 5bab8b4

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
@@ -1050,15 +1050,15 @@ param existingResourceName string
10501050
10511051
resource sqlServer 'Microsoft.Sql/servers@2021-11-01' existing = {
10521052
name: existingResourceName
1053+
}
1054+
1055+
resource sqlServer_admin 'Microsoft.Sql/servers/administrators@2021-11-01' = {
1056+
name: 'ActiveDirectory'
10531057
properties: {
1054-
administrators: {
1055-
administratorType: 'ActiveDirectory'
1056-
login: principalName
1057-
sid: principalId
1058-
tenantId: subscription().tenantId
1059-
azureADOnlyAuthentication: true
1060-
}
1058+
login: principalName
1059+
sid: principalId
10611060
}
1061+
parent: sqlServer
10621062
}
10631063
10641064
resource sqlFirewallRule_AllowAllAzureIps 'Microsoft.Sql/servers/firewallRules@2021-11-01' = {
@@ -1096,8 +1096,7 @@ public async Task SupportsExistingAzureSqlServerInRunMode()
10961096
"params": {
10971097
"existingResourceName": "{existingResourceName.value}",
10981098
"principalId": "",
1099-
"principalName": "",
1100-
"principalType": ""
1099+
"principalName": ""
11011100
}
11021101
}
11031102
""";
@@ -1114,20 +1113,17 @@ param principalName string
11141113
11151114
param existingResourceName string
11161115
1117-
param principalType string
1118-
11191116
resource sqlServer 'Microsoft.Sql/servers@2021-11-01' existing = {
11201117
name: existingResourceName
1118+
}
1119+
1120+
resource sqlServer_admin 'Microsoft.Sql/servers/administrators@2021-11-01' = {
1121+
name: 'ActiveDirectory'
11211122
properties: {
1122-
administrators: {
1123-
administratorType: 'ActiveDirectory'
1124-
principalType: principalType
1125-
login: principalName
1126-
sid: principalId
1127-
tenantId: subscription().tenantId
1128-
azureADOnlyAuthentication: true
1129-
}
1123+
login: principalName
1124+
sid: principalId
11301125
}
1126+
parent: sqlServer
11311127
}
11321128
11331129
resource sqlFirewallRule_AllowAllAzureIps 'Microsoft.Sql/servers/firewallRules@2021-11-01' = {
@@ -1300,13 +1296,13 @@ public async Task SupportsExistingAzureApplicationInsightsWithResourceGroup()
13001296
var expectedBicep = """
13011297
@description('The location for the resource(s) to be deployed.')
13021298
param location string = resourceGroup().location
1303-
1299+
13041300
param existingResourceName string
1305-
1301+
13061302
resource appInsights 'Microsoft.Insights/components@2020-02-02' existing = {
13071303
name: existingResourceName
13081304
}
1309-
1305+
13101306
output appInsightsConnectionString string = appInsights.properties.ConnectionString
13111307
""";
13121308

@@ -1347,17 +1343,17 @@ public async Task SupportsExistingAzureOpenAIWithResourceGroup()
13471343
var expectedBicep = """
13481344
@description('The location for the resource(s) to be deployed.')
13491345
param location string = resourceGroup().location
1350-
1346+
13511347
param existingResourceName string
1352-
1348+
13531349
param principalType string
1354-
1350+
13551351
param principalId string
1356-
1352+
13571353
resource openAI 'Microsoft.CognitiveServices/accounts@2024-10-01' existing = {
13581354
name: existingResourceName
13591355
}
1360-
1356+
13611357
resource openAI_CognitiveServicesOpenAIContributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
13621358
name: guid(openAI.id, principalId, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a001fd3d-188f-4b5d-821b-7da978bf7442'))
13631359
properties: {
@@ -1367,7 +1363,7 @@ param principalId string
13671363
}
13681364
scope: openAI
13691365
}
1370-
1366+
13711367
resource mymodel 'Microsoft.CognitiveServices/accounts/deployments@2024-10-01' = {
13721368
name: 'mymodel'
13731369
properties: {

0 commit comments

Comments
 (0)