Skip to content

Commit 0b36cd3

Browse files
committed
Respond to PR feedback
1 parent 573e974 commit 0b36cd3

File tree

4 files changed

+168
-2
lines changed

4 files changed

+168
-2
lines changed

src/Aspire.Hosting.Azure.Network/AzureSubnetResource.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,14 @@ internal SubnetResource ToProvisioningEntity(AzureResourceInfrastructure infra,
123123
});
124124
}
125125

126-
if (NetworkSecurityGroup is not null &&
127-
nsgMap.TryGetValue(NetworkSecurityGroup, out var provisioningNsg))
126+
if (NetworkSecurityGroup is not null)
128127
{
128+
if (!nsgMap.TryGetValue(NetworkSecurityGroup, out var provisioningNsg))
129+
{
130+
throw new InvalidOperationException(
131+
$"The Network Security Group '{NetworkSecurityGroup.Name}' referenced by subnet '{Name}' was not found in the parent Virtual Network's NetworkSecurityGroups collection.");
132+
}
133+
129134
// Set the NSG reference on the subnet by setting the model's Id property.
130135
// This produces the correct bicep: networkSecurityGroup: { id: nsg.id }
131136
subnet.NetworkSecurityGroup.Id = provisioningNsg.Id;

src/Aspire.Hosting.Azure.Network/AzureVirtualNetworkExtensions.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,15 @@ public static IResourceBuilder<AzureSubnetResource> WithNetworkSecurityGroup(
409409
ArgumentNullException.ThrowIfNull(builder);
410410
ArgumentNullException.ThrowIfNull(nsg);
411411

412+
if (nsg.Resource.Parent != builder.Resource.Parent)
413+
{
414+
throw new ArgumentException(
415+
$"The Network Security Group '{nsg.Resource.Name}' belongs to Virtual Network '{nsg.Resource.Parent.Name}', " +
416+
$"but the subnet '{builder.Resource.Name}' belongs to Virtual Network '{builder.Resource.Parent.Name}'. " +
417+
$"The NSG and subnet must belong to the same Virtual Network.",
418+
nameof(nsg));
419+
}
420+
412421
builder.Resource.NetworkSecurityGroup = nsg.Resource;
413422
return builder;
414423
}

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

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,4 +387,66 @@ public void WithSecurityRule_DuplicateName_Throws()
387387

388388
Assert.Contains("allow-https", exception.Message, StringComparison.OrdinalIgnoreCase);
389389
}
390+
391+
[Fact]
392+
public async Task MultipleNSGs_WithSameRuleName_GeneratesDistinctBicepIdentifiers()
393+
{
394+
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
395+
396+
var vnet = builder.AddAzureVirtualNetwork("myvnet");
397+
398+
var nsg1 = vnet.AddNetworkSecurityGroup("nsg-one")
399+
.WithSecurityRule(new AzureSecurityRule
400+
{
401+
Name = "allow-https",
402+
Priority = 100,
403+
Direction = SecurityRuleDirection.Inbound,
404+
Access = SecurityRuleAccess.Allow,
405+
Protocol = SecurityRuleProtocol.Tcp,
406+
SourceAddressPrefix = "*",
407+
SourcePortRange = "*",
408+
DestinationAddressPrefix = "*",
409+
DestinationPortRange = "443"
410+
});
411+
412+
var nsg2 = vnet.AddNetworkSecurityGroup("nsg-two")
413+
.WithSecurityRule(new AzureSecurityRule
414+
{
415+
Name = "allow-https",
416+
Priority = 100,
417+
Direction = SecurityRuleDirection.Inbound,
418+
Access = SecurityRuleAccess.Allow,
419+
Protocol = SecurityRuleProtocol.Tcp,
420+
SourceAddressPrefix = "VirtualNetwork",
421+
SourcePortRange = "*",
422+
DestinationAddressPrefix = "*",
423+
DestinationPortRange = "443"
424+
});
425+
426+
vnet.AddSubnet("subnet1", "10.0.1.0/24")
427+
.WithNetworkSecurityGroup(nsg1);
428+
vnet.AddSubnet("subnet2", "10.0.2.0/24")
429+
.WithNetworkSecurityGroup(nsg2);
430+
431+
var manifest = await AzureManifestUtils.GetManifestWithBicep(vnet.Resource);
432+
433+
await Verify(manifest.BicepText, extension: "bicep");
434+
}
435+
436+
[Fact]
437+
public void WithNetworkSecurityGroup_DifferentVNet_Throws()
438+
{
439+
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
440+
441+
var vnet1 = builder.AddAzureVirtualNetwork("vnet1");
442+
var vnet2 = builder.AddAzureVirtualNetwork("vnet2");
443+
444+
var nsg = vnet1.AddNetworkSecurityGroup("web-nsg");
445+
var subnet = vnet2.AddSubnet("web-subnet", "10.0.1.0/24");
446+
447+
var exception = Assert.Throws<ArgumentException>(() => subnet.WithNetworkSecurityGroup(nsg));
448+
449+
Assert.Contains("vnet1", exception.Message);
450+
Assert.Contains("vnet2", exception.Message);
451+
}
390452
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
@description('The location for the resource(s) to be deployed.')
2+
param location string = resourceGroup().location
3+
4+
resource myvnet 'Microsoft.Network/virtualNetworks@2025-05-01' = {
5+
name: take('myvnet-${uniqueString(resourceGroup().id)}', 64)
6+
properties: {
7+
addressSpace: {
8+
addressPrefixes: [
9+
'10.0.0.0/16'
10+
]
11+
}
12+
}
13+
location: location
14+
tags: {
15+
'aspire-resource-name': 'myvnet'
16+
}
17+
}
18+
19+
resource nsg_one 'Microsoft.Network/networkSecurityGroups@2025-05-01' = {
20+
name: take('nsg_one-${uniqueString(resourceGroup().id)}', 80)
21+
location: location
22+
}
23+
24+
resource nsg_one_allow_https 'Microsoft.Network/networkSecurityGroups/securityRules@2025-05-01' = {
25+
name: 'allow-https'
26+
properties: {
27+
access: 'Allow'
28+
destinationAddressPrefix: '*'
29+
destinationPortRange: '443'
30+
direction: 'Inbound'
31+
priority: 100
32+
protocol: 'Tcp'
33+
sourceAddressPrefix: '*'
34+
sourcePortRange: '*'
35+
}
36+
parent: nsg_one
37+
}
38+
39+
resource nsg_two 'Microsoft.Network/networkSecurityGroups@2025-05-01' = {
40+
name: take('nsg_two-${uniqueString(resourceGroup().id)}', 80)
41+
location: location
42+
}
43+
44+
resource nsg_two_allow_https 'Microsoft.Network/networkSecurityGroups/securityRules@2025-05-01' = {
45+
name: 'allow-https'
46+
properties: {
47+
access: 'Allow'
48+
destinationAddressPrefix: '*'
49+
destinationPortRange: '443'
50+
direction: 'Inbound'
51+
priority: 100
52+
protocol: 'Tcp'
53+
sourceAddressPrefix: 'VirtualNetwork'
54+
sourcePortRange: '*'
55+
}
56+
parent: nsg_two
57+
}
58+
59+
resource subnet1 'Microsoft.Network/virtualNetworks/subnets@2025-05-01' = {
60+
name: 'subnet1'
61+
properties: {
62+
addressPrefix: '10.0.1.0/24'
63+
networkSecurityGroup: {
64+
id: nsg_one.id
65+
}
66+
}
67+
parent: myvnet
68+
}
69+
70+
resource subnet2 'Microsoft.Network/virtualNetworks/subnets@2025-05-01' = {
71+
name: 'subnet2'
72+
properties: {
73+
addressPrefix: '10.0.2.0/24'
74+
networkSecurityGroup: {
75+
id: nsg_two.id
76+
}
77+
}
78+
parent: myvnet
79+
dependsOn: [
80+
subnet1
81+
]
82+
}
83+
84+
output subnet1_Id string = subnet1.id
85+
86+
output subnet2_Id string = subnet2.id
87+
88+
output id string = myvnet.id
89+
90+
output name string = myvnet.name

0 commit comments

Comments
 (0)