Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code Improvements #7

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
315 changes: 91 additions & 224 deletions src/Find-NextCidrRange/GetCidr.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<IActionResult> 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<IPNetwork2>();

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<IPNetwork2>();

// 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<IPNetwork2> 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
}

}
}
}
2 changes: 1 addition & 1 deletion terraform/fnc-app.tf
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ module "fnc_app" {
http2_enabled = true

application_stack = {
dotnet_version = "6.0"
dotnet_version = "8.0"
}
}

Expand Down