From 3cd3e1e6558c6555c0b0369a764dfa0a9ee44f1d Mon Sep 17 00:00:00 2001 From: Daniel Harris Date: Wed, 24 Apr 2024 17:05:05 +0100 Subject: [PATCH 1/3] Update GetCidr.cs --- src/Find-NextCidrRange/GetCidr.cs | 308 +++++++++--------------------- 1 file changed, 95 insertions(+), 213 deletions(-) diff --git a/src/Find-NextCidrRange/GetCidr.cs b/src/Find-NextCidrRange/GetCidr.cs index 671b39d..d3f807b 100644 --- a/src/Find-NextCidrRange/GetCidr.cs +++ b/src/Find-NextCidrRange/GetCidr.cs @@ -38,13 +38,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE using System.Text.Json; using System.Threading.Tasks; - -namespace FindNextCIDR -{ - public static class GetCidr - { - public class ProposedSubnetResponse - { +namespace FindNextCIDR { + public static class GetCidr { + public class ProposedSubnetResponse { public string name { get; set; } public string id { get; set; } public string type { get; set; } @@ -52,251 +48,137 @@ public class ProposedSubnetResponse public string addressSpace { get; set; } public string proposedCIDR { get; set; } } - public class CustomError - { + public class CustomError { public string code { get; set; } public string message { get; set; } } - static HttpStatusCode httpStatusCode = HttpStatusCode.OK; - [FunctionName("GetCidr")] public static async Task Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, - ILogger log) - { + ILogger log) { log.LogInformation("C# HTTP trigger function processed a request."); + // Check for valid input + string[] requiredParameters = { "subscriptionId", "virtualNetworkName", "resourceGroupName", "cidr" }; + string missingParameter = requiredParameters.FirstOrDefault(parameter => string.IsNullOrWhiteSpace(req.Query[parameter])); + if(missingParameter != null) return ResultError($"{missingParameter} is null or empty", HttpStatusCode.BadRequest); + + // Get the query parameters string subscriptionId = req.Query["subscriptionId"]; - string virtualNetworkName = req.Query["virtualNetworkName"]; - string resourceGroupName = req.Query["resourceGroupName"]; + string vnetName = req.Query["virtualNetworkName"]; + string rgName = req.Query["resourceGroupName"]; string cidrString = req.Query["cidr"]; string desiredAddressSpace = req.Query["addressSpace"]; - Exception error = null; - string errorMessage = null; - bool success = false; - string foundSubnet = null; - string foundAddressSpace = null; - byte cidr; - VirtualNetworkResource vNet = null; - - try - { - // Validate the input params - errorMessage = ValidateInput(subscriptionId, virtualNetworkName, resourceGroupName, cidrString, desiredAddressSpace); - if (null == errorMessage) - { - // Make sure the CIDR is valid - if (ValidateCIDR(cidrString)) - { - cidr = Byte.Parse(cidrString); - - // Get a client for the SDK calls - var armClient = new ArmClient(new DefaultAzureCredential(), subscriptionId); - - var subscription = await armClient.GetDefaultSubscriptionAsync(); - ResourceGroupResource rg = await subscription.GetResourceGroupAsync(resourceGroupName); - - vNet = await rg.GetVirtualNetworkAsync(virtualNetworkName); - - var vNetCIDRs = new HashSet(); - - foreach (string ip in vNet.Data.AddressPrefixes) - { - IPNetwork2 vNetCIDR = IPNetwork2.Parse(ip); - if (cidr >= vNetCIDR.Cidr && (null == desiredAddressSpace || vNetCIDR.ToString().Equals(desiredAddressSpace))) - { - log.LogInformation("In: Candidate = " + vNetCIDR.ToString() + ", desired = " + desiredAddressSpace); - foundSubnet = GetValidSubnetIfExists(vNet, vNetCIDR, cidr); - foundAddressSpace = vNetCIDR.ToString(); - - if (null != foundSubnet) - { - log.LogInformation("Valid subnet is found: " + foundSubnet); - success = true; - break; - } - } - } - if (!success) - { - httpStatusCode = HttpStatusCode.NotFound; - if (null == desiredAddressSpace) - errorMessage = "VNet " + resourceGroupName + "/" + virtualNetworkName + " cannot accept a subnet of size " + cidr; - else - errorMessage = "Requested address space (" + desiredAddressSpace + ") not found in VNet " + resourceGroupName + "/" + virtualNetworkName; + // Validate the CIDR block and CIDR size + if (!ValidateCIDR(cidrString)) return ResultError("Invalid CIDR size requested: " + cidrString); + if (!ValidateCIDRBlock(desiredAddressSpace)) return ResultError("desiredAddressSpace is invalid"); + + try { + // Get a client for the SDK calls + var armClient = new ArmClient(new DefaultAzureCredential(), subscriptionId); + var subscription = await armClient.GetDefaultSubscriptionAsync(); + ResourceGroupResource rg = await subscription.GetResourceGroupAsync(rgName); + VirtualNetworkResource vNet = await rg.GetVirtualNetworkAsync(vnetName); + byte cidr = Byte.Parse(cidrString); + + foreach (string ip in vNet.Data.AddressPrefixes) { + IPNetwork2 vNetCIDR = IPNetwork2.Parse(ip); + if (cidr >= vNetCIDR.Cidr && (null == desiredAddressSpace || vNetCIDR.ToString().Equals(desiredAddressSpace))) { + log.LogInformation("In: Candidate = " + vNetCIDR.ToString() + ", desired = " + desiredAddressSpace); + string foundSubnet = GetValidSubnetIfExists(vNet, vNetCIDR, cidr); + string foundAddressSpace = vNetCIDR.ToString(); + + if (foundSubnet != null) { + log.LogInformation("Valid subnet is found: " + foundSubnet); + return ResultSuccess(vNet, vnetName, foundSubnet, foundAddressSpace); } - - - } - else - { - httpStatusCode = HttpStatusCode.BadRequest; - errorMessage = "Invalid CIDR size requested: " + cidrString; } } - else - { - httpStatusCode = HttpStatusCode.BadRequest; - errorMessage = "Invalid input: " + errorMessage; - } - } + var matchingPrefixes = vNet.Data.AddressPrefixes + .Select(prefix => IPNetwork2.Parse(prefix)) + .Where(vNetCIDR => cidr >= vNetCIDR.Cidr && (desiredAddressSpace == null || vNetCIDR.ToString().Equals(desiredAddressSpace))); - catch (RequestFailedException ex) when (ex.Status == 404) // case the resource group or vnet doesn't exist - { - httpStatusCode = HttpStatusCode.NotFound; - error = ex; - } - catch (Exception e) - { + foreach (var vNetCIDR in matchingPrefixes) { + log.LogInformation("In: Candidate = " + vNetCIDR.ToString() + ", desired = " + desiredAddressSpace); + string foundSubnet = GetValidSubnetIfExists(vNet, vNetCIDR, cidr); + string foundAddressSpace = vNetCIDR.ToString(); - httpStatusCode = HttpStatusCode.InternalServerError; - error = e; - // empty code var will signal error - } + if (foundSubnet != null) { + log.LogInformation("Valid subnet is found: " + foundSubnet); + return ResultSuccess(vNet, vnetName, foundSubnet, foundAddressSpace); + } + } - ObjectResult result; - if (null == errorMessage && success) - { - ProposedSubnetResponse proposedSubnetResponse = new ProposedSubnetResponse() - { - name = virtualNetworkName, - id = vNet.Id, - type = vNet.Id.ResourceType, - location = vNet.Data.Location, - proposedCIDR = foundSubnet, - addressSpace = foundAddressSpace - }; + string errMsg = desiredAddressSpace == null + ? "VNet " + rgName + "/" + vnetName + " cannot accept a subnet of size " + cidr + : "Requested address space (" + desiredAddressSpace + ") not found in VNet " + rgName + "/" + vnetName; - var options = new JsonSerializerOptions { WriteIndented = true }; - string jsonString = JsonSerializer.Serialize(proposedSubnetResponse, options); + return ResultError(errMsg, HttpStatusCode.NotFound); - result = new OkObjectResult(jsonString); } - else - { if(null != error) - { - errorMessage = error.Message; - } - var customError = new CustomError { - code = "" + ((int)httpStatusCode), - message = httpStatusCode.ToString() + ", " + errorMessage - }; - - var options = new JsonSerializerOptions { WriteIndented = true }; - string jsonString = JsonSerializer.Serialize(customError, options); - result = new BadRequestObjectResult(jsonString); - } + // case the resource group or vnet doesn't exist + catch (RequestFailedException ex) when (ex.Status == 404) { return ResultError(ex, HttpStatusCode.NotFound); } - return result; + // empty code var will signal error + catch (Exception e) { return ResultError(e, HttpStatusCode.InternalServerError); } } + private static BadRequestObjectResult ResultError(string errorMessage, HttpStatusCode httpStatusCode = HttpStatusCode.BadRequest) { + var customError = new CustomError { + code = "" + ((int)httpStatusCode), + message = httpStatusCode.ToString() + ", " + errorMessage + }; - private static string ValidateInput(string subscriptionId, string virtualNetworkName, string resourceGroupName, string cidrString, string desiredAddressSpace) - { - string errorMessage = null; + var options = new JsonSerializerOptions { WriteIndented = true }; + string jsonString = JsonSerializer.Serialize(customError, options); - if (null == subscriptionId) - { - errorMessage = "subscriptionId is null"; - } - else if (null == virtualNetworkName) - { - errorMessage = "virtualNetworkName is null"; - } - else if (null == resourceGroupName) - { - errorMessage = "resourceGroupName is null"; - } - else if (null == cidrString) - { - errorMessage = "cidr is null"; - } - else if (!ValidateCIDRBlock(desiredAddressSpace)) - { - errorMessage = "desiredAddressSpace is invalid"; - } - - return errorMessage; + return new BadRequestObjectResult(jsonString);; } - - private static bool ValidateCIDRBlock(string inCIDRBlock) - { - bool isGood = false; - - if (null == inCIDRBlock) - { - isGood = true; - } - else - { - try - { - // IPAddress.Parse(inCIDRBlock); - IPNetwork2.Parse(inCIDRBlock); - isGood = true; - } catch - { - isGood = false; - } - } - - return isGood; + private static OkObjectResult ResultSuccess(VirtualNetworkResource vNet, string virtualNetworkName, string foundSubnet, string foundAddressSpace) { + ProposedSubnetResponse proposedSubnetResponse = new ProposedSubnetResponse() { + name = virtualNetworkName, + id = vNet.Id, + type = vNet.Id.ResourceType, + location = vNet.Data.Location, + proposedCIDR = foundSubnet, + addressSpace = foundAddressSpace + }; + + var options = new JsonSerializerOptions { WriteIndented = true }; + string jsonString = JsonSerializer.Serialize(proposedSubnetResponse, options); + + return new OkObjectResult(jsonString); } - private static bool ValidateCIDR(string inCIDR) - { - bool isGood = false; - - byte cidr; + private static bool ValidateCIDRBlock(string inCIDRBlock) { + if (inCIDRBlock == null) return true; // no address space specified (ok as optional) - if(Byte.TryParse(inCIDR, out cidr)) - { - isGood = (2 <= cidr && 29 >= cidr); - } + try { IPNetwork2.Parse(inCIDRBlock); } + catch { return false; } - return isGood; + return true; } - private static string GetValidSubnetIfExists(VirtualNetworkResource vNet, IPNetwork2 vNetCIDR, Byte cidr) - { - var usedSubnets = new List(); - - // Get every Azure subnet in the VNet - SubnetCollection usedSubnetsAzure = vNet.GetSubnets(); - - // Get a list of all CIDRs that could possibly fit into the given address space with the CIDR range requested - IPNetworkCollection candidateSubnets = vNetCIDR.Subnet(cidr); + private static bool ValidateCIDR(string inCIDR) { + if (Byte.TryParse(inCIDR, out global::System.Byte cidr)) return 2 <= cidr && 29 >= cidr; + else return false; + } - // Convert into IPNetwork object list - foreach (SubnetResource usedSubnet in usedSubnetsAzure) - { - usedSubnets.Add(IPNetwork2.Parse(usedSubnet.Data.AddressPrefix)); - } + private static string GetValidSubnetIfExists(VirtualNetworkResource vNet, IPNetwork2 requestedCIDR, byte cidr) { + List subnets = vNet.GetSubnets().Select(subnet => IPNetwork2.Parse(subnet.Data.AddressPrefix)).ToList(); - foreach (IPNetwork2 candidateSubnet in candidateSubnets) - { - bool subnetIsValid = true; - // Go through each Azure subnet in VNet, check against candidate - foreach (IPNetwork2 usedSubnet in usedSubnets) - { - if (usedSubnet.Overlap(candidateSubnet)) - { - subnetIsValid = false; - break; // stop the loop as the candidate is not valid (overlapping with existing subnets) - } - } - if (subnetIsValid) - { - return candidateSubnet.ToString(); + // Iterate through each candidate subnet + foreach (IPNetwork2 candidateSubnet in requestedCIDR.Subnet(cidr)) { + // Check if the candidate subnet overlaps with any existing subnet + if (!subnets.Any(subnet => subnet.Overlap(candidateSubnet))) { + return candidateSubnet.ToString(); // Found a valid subnet, return it } } - // no valid subnet found - return null; + return null; // No valid subnet found } - } -} \ No newline at end of file +} From 80fbc54f2ad0191fdbf2fae88d6b567a9f694349 Mon Sep 17 00:00:00 2001 From: Daniel Harris Date: Thu, 25 Apr 2024 11:17:01 +0100 Subject: [PATCH 2/3] Update GetCidr.cs --- src/Find-NextCidrRange/GetCidr.cs | 95 +++++++++++++++---------------- 1 file changed, 45 insertions(+), 50 deletions(-) diff --git a/src/Find-NextCidrRange/GetCidr.cs b/src/Find-NextCidrRange/GetCidr.cs index d3f807b..55c1c7e 100644 --- a/src/Find-NextCidrRange/GetCidr.cs +++ b/src/Find-NextCidrRange/GetCidr.cs @@ -41,16 +41,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE namespace FindNextCIDR { public static class GetCidr { public class ProposedSubnetResponse { - public string name { get; set; } - public string id { get; set; } - public string type { get; set; } - public string location { get; set; } - public string addressSpace { get; set; } - public string proposedCIDR { get; set; } + public required string Name { get; set; } + public required string ID { get; set; } + public required string Type { get; set; } + public required string Location { get; set; } + public required string AddressSpace { get; set; } + public required string {roposedCIDR { get; set; } } public class CustomError { - public string code { get; set; } - public string message { get; set; } + public required string Code { get; set; } + public required string Message { get; set; } } [FunctionName("GetCidr")] @@ -75,62 +75,57 @@ public static async Task Run( if (!ValidateCIDR(cidrString)) return ResultError("Invalid CIDR size requested: " + cidrString); if (!ValidateCIDRBlock(desiredAddressSpace)) return ResultError("desiredAddressSpace is invalid"); + ResourceGroupResource rg; + VirtualNetworkResource vNet; try { - // Get a client for the SDK calls var armClient = new ArmClient(new DefaultAzureCredential(), subscriptionId); var subscription = await armClient.GetDefaultSubscriptionAsync(); - ResourceGroupResource rg = await subscription.GetResourceGroupAsync(rgName); - VirtualNetworkResource vNet = await rg.GetVirtualNetworkAsync(vnetName); - byte cidr = Byte.Parse(cidrString); - - foreach (string ip in vNet.Data.AddressPrefixes) { - IPNetwork2 vNetCIDR = IPNetwork2.Parse(ip); - if (cidr >= vNetCIDR.Cidr && (null == desiredAddressSpace || vNetCIDR.ToString().Equals(desiredAddressSpace))) { - log.LogInformation("In: Candidate = " + vNetCIDR.ToString() + ", desired = " + desiredAddressSpace); - string foundSubnet = GetValidSubnetIfExists(vNet, vNetCIDR, cidr); - string foundAddressSpace = vNetCIDR.ToString(); - - if (foundSubnet != null) { - log.LogInformation("Valid subnet is found: " + foundSubnet); - return ResultSuccess(vNet, vnetName, foundSubnet, foundAddressSpace); - } - } - } - - var matchingPrefixes = vNet.Data.AddressPrefixes - .Select(prefix => IPNetwork2.Parse(prefix)) - .Where(vNetCIDR => cidr >= vNetCIDR.Cidr && (desiredAddressSpace == null || vNetCIDR.ToString().Equals(desiredAddressSpace))); + rg = await subscription.GetResourceGroupAsync(rgName); + vNet = await rg.GetVirtualNetworkAsync(vnetName); + } + catch (RequestFailedException ex) when (ex.Status == 404) { + // case the resource group or vnet doesn't exist + return ResultError(ex.ToString(), HttpStatusCode.NotFound); + } + catch (Exception e) { + // empty code var will signal error + return ResultError(e.ToString(), HttpStatusCode.InternalServerError); + } - foreach (var vNetCIDR in matchingPrefixes) { - log.LogInformation("In: Candidate = " + vNetCIDR.ToString() + ", desired = " + desiredAddressSpace); + byte cidr = Byte.Parse(cidrString); + foreach (string ip in vNet.Data.AddressPrefixes) { + IPNetwork2 vNetCIDR = IPNetwork2.Parse(ip); + if (cidr >= vNetCIDR.Cidr && (null == desiredAddressSpace || vNetCIDR.ToString().Equals(desiredAddressSpace))) { string foundSubnet = GetValidSubnetIfExists(vNet, vNetCIDR, cidr); string foundAddressSpace = vNetCIDR.ToString(); if (foundSubnet != null) { - log.LogInformation("Valid subnet is found: " + foundSubnet); return ResultSuccess(vNet, vnetName, foundSubnet, foundAddressSpace); } } + } + var matchingPrefixes = vNet.Data.AddressPrefixes + .Select(prefix => IPNetwork2.Parse(prefix)) + .Where(vNetCIDR => cidr >= vNetCIDR.Cidr && (desiredAddressSpace == null || vNetCIDR.ToString().Equals(desiredAddressSpace))); - string errMsg = desiredAddressSpace == null - ? "VNet " + rgName + "/" + vnetName + " cannot accept a subnet of size " + cidr - : "Requested address space (" + desiredAddressSpace + ") not found in VNet " + rgName + "/" + vnetName; - - return ResultError(errMsg, HttpStatusCode.NotFound); + foreach (var vNetCIDR in matchingPrefixes) { + string foundSubnet = GetValidSubnetIfExists(vNet, vNetCIDR, cidr); + string foundAddressSpace = vNetCIDR.ToString(); + if (foundSubnet != null) return ResultSuccess(vNet, vnetName, foundSubnet, foundAddressSpace); } - // case the resource group or vnet doesn't exist - catch (RequestFailedException ex) when (ex.Status == 404) { return ResultError(ex, HttpStatusCode.NotFound); } + string errMsg = desiredAddressSpace == null + ? "VNet " + rgName + "/" + vnetName + " cannot accept a subnet of size " + cidr + : "Requested address space (" + desiredAddressSpace + ") not found in VNet " + rgName + "/" + vnetName; - // empty code var will signal error - catch (Exception e) { return ResultError(e, HttpStatusCode.InternalServerError); } + return ResultError(errMsg, HttpStatusCode.NotFound); } private static BadRequestObjectResult ResultError(string errorMessage, HttpStatusCode httpStatusCode = HttpStatusCode.BadRequest) { var customError = new CustomError { - code = "" + ((int)httpStatusCode), - message = httpStatusCode.ToString() + ", " + errorMessage + Code = "" + ((int)httpStatusCode), + Message = httpStatusCode.ToString() + ", " + errorMessage }; var options = new JsonSerializerOptions { WriteIndented = true }; @@ -140,12 +135,12 @@ private static BadRequestObjectResult ResultError(string errorMessage, HttpStatu } private static OkObjectResult ResultSuccess(VirtualNetworkResource vNet, string virtualNetworkName, string foundSubnet, string foundAddressSpace) { ProposedSubnetResponse proposedSubnetResponse = new ProposedSubnetResponse() { - name = virtualNetworkName, - id = vNet.Id, - type = vNet.Id.ResourceType, - location = vNet.Data.Location, - proposedCIDR = foundSubnet, - addressSpace = foundAddressSpace + Name = virtualNetworkName, + ID = vNet.Id, + Type = vNet.Id.ResourceType, + Location = vNet.Data.Location, + ProposedCIDR = foundSubnet, + AddressSpace = foundAddressSpace }; var options = new JsonSerializerOptions { WriteIndented = true }; From 9a16e49e8ca7995fb36f9964e3f8b2761ed10281 Mon Sep 17 00:00:00 2001 From: Daniel Harris Date: Tue, 7 May 2024 15:03:30 +0100 Subject: [PATCH 3/3] - Fixed typo - Upgraded to .NET 8.0 - Added Linq using - Removed duplicated section of code --- src/Find-NextCidrRange/GetCidr.cs | 14 ++------------ terraform/fnc-app.tf | 2 +- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/Find-NextCidrRange/GetCidr.cs b/src/Find-NextCidrRange/GetCidr.cs index 55c1c7e..b5d3c5d 100644 --- a/src/Find-NextCidrRange/GetCidr.cs +++ b/src/Find-NextCidrRange/GetCidr.cs @@ -37,6 +37,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE using System.Net; using System.Text.Json; using System.Threading.Tasks; +using System.Linq; namespace FindNextCIDR { public static class GetCidr { @@ -46,7 +47,7 @@ public class ProposedSubnetResponse { public required string Type { get; set; } public required string Location { get; set; } public required string AddressSpace { get; set; } - public required string {roposedCIDR { get; set; } + public required string ProposedCIDR { get; set; } } public class CustomError { public required string Code { get; set; } @@ -93,17 +94,6 @@ public static async Task Run( } byte cidr = Byte.Parse(cidrString); - foreach (string ip in vNet.Data.AddressPrefixes) { - IPNetwork2 vNetCIDR = IPNetwork2.Parse(ip); - if (cidr >= vNetCIDR.Cidr && (null == desiredAddressSpace || vNetCIDR.ToString().Equals(desiredAddressSpace))) { - string foundSubnet = GetValidSubnetIfExists(vNet, vNetCIDR, cidr); - string foundAddressSpace = vNetCIDR.ToString(); - - if (foundSubnet != null) { - return ResultSuccess(vNet, vnetName, foundSubnet, foundAddressSpace); - } - } - } var matchingPrefixes = vNet.Data.AddressPrefixes .Select(prefix => IPNetwork2.Parse(prefix)) diff --git a/terraform/fnc-app.tf b/terraform/fnc-app.tf index 5d5050a..25f3f32 100644 --- a/terraform/fnc-app.tf +++ b/terraform/fnc-app.tf @@ -40,7 +40,7 @@ module "fnc_app" { http2_enabled = true application_stack = { - dotnet_version = "6.0" + dotnet_version = "8.0" } }