From 63bd4cd95f0b1a66948584823acc4eabb37b9244 Mon Sep 17 00:00:00 2001 From: David Gardiner Date: Thu, 30 Jan 2025 23:07:37 +1030 Subject: [PATCH] Handle when VNet is empty Fixes #34 --- Find-FreeSubnets-NoExisting.Expected.json | 12 +++++ VNet.Tests.ps1 | 56 +++++++++++++++++++++++ VNet.psm1 | 11 ++++- 3 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 Find-FreeSubnets-NoExisting.Expected.json diff --git a/Find-FreeSubnets-NoExisting.Expected.json b/Find-FreeSubnets-NoExisting.Expected.json new file mode 100644 index 0000000..1ab96ed --- /dev/null +++ b/Find-FreeSubnets-NoExisting.Expected.json @@ -0,0 +1,12 @@ +{ + "VNetStart": "10.1.0.0", + "VNetEnd": "10.1.255.255", + "Available": [ + { + "Start": "10.1.0.0", + "End": "10.1.255.255", + "Size": 65536, + "CIDRAvailable": "{10.1.0.0/25, 10.1.0.0/26, 10.1.0.0/27, 10.1.0.0/28…}" + } + ] +} \ No newline at end of file diff --git a/VNet.Tests.ps1 b/VNet.Tests.ps1 index 9fa7e93..33a82a2 100644 --- a/VNet.Tests.ps1 +++ b/VNet.Tests.ps1 @@ -153,4 +153,60 @@ Describe "Find-FreeSubnets" { $expected = [VNetSummary](Get-content .\Find-FreeSubnets.Expected.json | ConvertFrom-Json) $result | Should -BeVNetSummary -ExpectedValue $expected } + + It "Returns expected output with no existing subnets" { + + $vnetWithNoSubnets = @" +{ + "AddressSpace": { + "AddressPrefixes": [ + "10.1.0.0/16" + ] + }, + "DhcpOptions": { + "DnsServers": null + }, + "FlowTimeoutInMinutes": null, + "Subnets": [], + "BgpCommunities": null, + "Encryption": { + "Enabled": "False", + "Enforcement": "AllowUnencrypted" + }, + "VirtualNetworkPeerings": [], + "ProvisioningState": "Succeeded", + "EnableDdosProtection": false, + "DdosProtectionPlan": null, + "IpAllocations": [], + "ExtendedLocation": null, + "ResourceGroupName": "rg-freesubnet-australiaeast", + "Location": "australiaeast", + "ResourceGuid": "6dac3c19-4583-4a63-bbb3-38746d1f6aab", + "Type": "Microsoft.Network/virtualNetworks", + "Tag": {}, + "TagsTable": null, + "Name": "vnet-freesubnet2-australiaeast", + "Etag": "W/\"28d22421-aaea-47e1-a456-f6c3b413e7a8\"", + "Id": "/subscriptions/b4b2e7e9-66e7-46b5-a56b-cce2b50011d4/resourceGroups/rg-freesubnet-australiaeast/providers/Microsoft.Network/virtualNetworks/vnet-freesubnet2-australiaeast", + "AddressSpaceText": "{\r\n \"AddressPrefixes\": [\r\n \"10.1.0.0/16\"\r\n ]\r\n}", + "DhcpOptionsText": "{}", + "FlowTimeoutInMinutesText": "null", + "SubnetsText": "[]", + "BgpCommunitiesText": "null", + "EncryptionText": "{\r\n \"Enabled\": \"False\",\r\n \"Enforcement\": \"AllowUnencrypted\"\r\n}", + "VirtualNetworkPeeringsText": "[]", + "EnableDdosProtectionText": "false", + "DdosProtectionPlanText": "null", + "IpAllocationsText": "[]", + "ExtendedLocationText": "null" +} +"@ | ConvertFrom-Json + + Mock -ModuleName VNet -CommandName Get-AzVirtualNetwork { return $vnetWithNoSubnets } + + $result = Find-FreeSubnets -ResourceGroup rg-freesubnet-australiaeast -VNetName vnet-freesubnet2-australiaeast + + $expected = [VNetSummary](Get-content .\Find-FreeSubnets-NoExisting.Expected.json | ConvertFrom-Json) + $result | Should -BeVNetSummary -ExpectedValue $expected + } } diff --git a/VNet.psm1 b/VNet.psm1 index fc7c941..eac1966 100644 --- a/VNet.psm1 +++ b/VNet.psm1 @@ -145,7 +145,14 @@ function Find-FreeSubnets { $afterLastAvailableCidr = "$afterLastAvailable/31" - $sorted = @($vnet.Subnets.AddressPrefix) + $afterLastAvailableCidr | Sort-Object -Property { + # create fake subnet immediately before the start of the VNet + $ipNum = [Net.IPAddress]::NetworkToHostOrder([BitConverter]::ToInt32($vnetStart.GetAddressBytes(), 0)) - 1 + $bytes = [BitConverter]::GetBytes([Net.IPAddress]::HostToNetworkOrder($ipNum)) + [Net.IPAddress] $beforeFirstAvailable = New-Object Net.IPAddress -ArgumentList (, $bytes) + + $beforeFirstAvailableCidr = "$beforeFirstAvailable/31" + + $sorted = @($beforeFirstAvailableCidr) + @($vnet.Subnets.AddressPrefix) + @($afterLastAvailableCidr) | Sort-Object -Property { $addr, $maskLength = $_ -split '/' $ip = ([Net.IPAddress] $addr) @@ -207,7 +214,7 @@ function Find-FreeSubnets { $notFirst = $true - if ($cidr -ne $afterLastAvailableCidr) { + if ($cidr -ne $afterLastAvailableCidr -and $cidr -ne $beforeFirstAvailableCidr) { $subnet = [Subnet]::new() $subnet.CIDR = $cidr $subnet.Start = $start