diff --git a/src/Find-NextCidrRange/GetCidr.cs b/src/Find-NextCidrRange/GetCidr.cs index 671b39d..b5d3c5d 100644 --- a/src/Find-NextCidrRange/GetCidr.cs +++ b/src/Find-NextCidrRange/GetCidr.cs @@ -37,266 +37,133 @@ 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; - - -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; } +using System.Linq; + +namespace FindNextCIDR { + public static class GetCidr { + public class ProposedSubnetResponse { + 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 ProposedCIDR { get; set; } } - public class CustomError - { - public string code { get; set; } - public string message { get; set; } + public class CustomError { + public required string Code { get; set; } + public required 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; - } - - - } - else - { - httpStatusCode = HttpStatusCode.BadRequest; - errorMessage = "Invalid CIDR size requested: " + cidrString; - } - } - - else - { - httpStatusCode = HttpStatusCode.BadRequest; - errorMessage = "Invalid input: " + errorMessage; - } + // 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"); + + ResourceGroupResource rg; + VirtualNetworkResource vNet; + try { + var armClient = new ArmClient(new DefaultAzureCredential(), subscriptionId); + var subscription = await armClient.GetDefaultSubscriptionAsync(); + 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 - { - httpStatusCode = HttpStatusCode.NotFound; - error = ex; + 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) - { - - httpStatusCode = HttpStatusCode.InternalServerError; - error = e; + catch (Exception e) { // empty code var will signal error + return ResultError(e.ToString(), HttpStatusCode.InternalServerError); } - 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 + byte cidr = Byte.Parse(cidrString); - }; + var matchingPrefixes = vNet.Data.AddressPrefixes + .Select(prefix => IPNetwork2.Parse(prefix)) + .Where(vNetCIDR => cidr >= vNetCIDR.Cidr && (desiredAddressSpace == null || vNetCIDR.ToString().Equals(desiredAddressSpace))); - var options = new JsonSerializerOptions { WriteIndented = true }; - string jsonString = JsonSerializer.Serialize(proposedSubnetResponse, options); + foreach (var vNetCIDR in matchingPrefixes) { + string foundSubnet = GetValidSubnetIfExists(vNet, vNetCIDR, cidr); + string foundAddressSpace = vNetCIDR.ToString(); - result = new OkObjectResult(jsonString); + if (foundSubnet != null) return ResultSuccess(vNet, vnetName, foundSubnet, foundAddressSpace); } - 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); - } + 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 result; + 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 + }; - 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; + private static bool ValidateCIDRBlock(string inCIDRBlock) { + if (inCIDRBlock == null) return true; // no address space specified (ok as optional) - byte cidr; + try { IPNetwork2.Parse(inCIDRBlock); } + catch { return false; } - if(Byte.TryParse(inCIDR, out cidr)) - { - isGood = (2 <= cidr && 29 >= cidr); - } - - 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 +} 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" } }