Webapp/SDAF/Controllers/Helper.cs (400 lines of code) (raw):

// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using SDAFWebApp.Models; using SDAFWebApp.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Reflection; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; namespace SDAFWebApp.Controllers { public class Helper : Controller { public static readonly Dictionary<string, string> regionMapping = new() { {"australiacentral", "auce"}, {"australiacentral2", "auc2"}, {"australiaeast", "auea"}, {"australiasoutheast", "ause"}, {"brazilsouth", "brso"}, {"brazilsoutheast", "brse"}, {"brazilus", "brus"}, {"canadacentral", "cace"}, {"canadaeast", "caea"}, {"centralindia", "cein"}, {"centralus", "ceus"}, {"centraluseuap", "ceua"}, {"eastasia", "eaas"}, {"eastus", "eaus"}, {"eastus2", "eus2"}, {"eastus2euap", "eusa"}, {"eastusstg", "eusg"}, {"francecentral", "frce"}, {"francesouth", "frso"}, {"germanynorth", "geno"}, {"germanywestcentral", "gewc"}, {"japaneast", "jaea"}, {"japanwest", "jawe"}, {"jioindiacentral", "jinc"}, {"jioindiawest", "jinw"}, {"koreacentral", "koce"}, {"koreasouth", "koso"}, {"northcentralus", "ncus"}, {"northeurope", "noeu"}, {"norwayeast", "noea"}, {"norwaywest", "nowe"}, {"qatarcentral", "qace"}, {"southafricanorth", "sano"}, {"southafricawest", "sawe"}, {"southcentralus", "scus"}, {"southcentralusstg", "scug"}, {"southeastasia", "soea"}, {"southindia", "soin"}, {"swedencentral", "sece"}, {"swedensouth", "seso"}, {"switzerlandnorth", "swno"}, {"switzerlandwest", "swwe"}, {"uaecentral", "uace"}, {"uaenorth", "uano"}, {"uksouth", "ukso"}, {"ukwest", "ukwe"}, {"westcentralus", "wcus"}, {"westeurope", "weeu"}, {"westindia", "wein"}, {"westus", "weus"}, {"westus2", "wus2"}, {"westus3", "wus3"} }; public static string ConvertToTerraform<T>(T model) { string modelType = (model.GetType() == typeof(LandscapeModel)) ? "Landscape" : "System"; string path = $"ParameterDetails/{modelType}Template.txt"; if (!System.IO.File.Exists(path)) { return $"Error generating terraform variables file: No template file {path} was found"; } StringBuilder stringBuilder = new(); foreach (string line in System.IO.File.ReadLines(path)) { string lineToAdd = line; Regex paramRegex = new(@"\$\$\w*\$\$"); if (paramRegex.IsMatch(line)) { string parameter = paramRegex.Match(line).Value.Trim('$'); PropertyInfo property = model.GetType().GetProperty(parameter); if (property == null) { lineToAdd = "#" + parameter + " = null"; } else { lineToAdd = WriteTfLine(property, model); } } stringBuilder.AppendLine(lineToAdd); } return stringBuilder.ToString(); } // Create the text that sets a model property to its value for a tfvars file public static string WriteTfLine<T>(PropertyInfo property, T model) { StringBuilder str = new(); var value = property.GetValue(model); if (property.PropertyType.GetElementType() == typeof(Tag)) { if (value == null) return "#" + property.Name + " = {}"; str.AppendLine(property.Name + " = {"); foreach (Tag t in (Tag[])value) { if (t.Key != null && t.Key.Length > 0) { str.AppendLine($" \"{t.Key}\" = \"{t.Value}\","); } } str.Append("}"); } else if (property.PropertyType.IsArray) { if (value == null) return "#" + property.Name + " = []"; str.Append(property.Name + " = ["); foreach (var val in (string[])value) { str.Append($"\"{val}\", "); } str.Remove(str.Length - 2, 2); str.Append("]"); } else if (property.PropertyType == typeof(Image)) { if (value == null) return "#" + property.Name + " = {}"; Image img = (Image)value; if (img.IsInitialized) { str.AppendLine(property.Name + " = {"); str.AppendLine(" os_type = " + $"\"{img.os_type}\","); str.AppendLine(" source_image_id = " + $"\"{img.source_image_id}\","); str.AppendLine(" publisher = " + $"\"{img.publisher}\","); str.AppendLine(" offer = " + $"\"{img.offer}\","); str.AppendLine(" sku = " + $"\"{img.sku}\","); str.AppendLine(" version = " + $"\"{img.version}\","); str.AppendLine(" type = " + $"\"{img.type}\""); str.Append("}"); } else { return "#" + property.Name + " = {}"; } } else if (property.PropertyType == typeof(bool?)) { if (value == null) return "#" + property.Name + " = false"; bool b = (bool)value; str.Append(property.Name + " = " + b.ToString().ToLower()); } else if (property.PropertyType == typeof(int?)) { if (value == null) return "#" + property.Name + " = 0"; int i = (int)value; str.Append(property.Name + " = " + i); } else { if (value == null) return "#" + property.Name + " = \"\""; str.Append(property.Name + " = " + $"\"{value}\""); } return str.ToString(); } public static StringContent CreateHttpContent(string changeType, string path, string content, GitRequestBody requestBody) { Commit commit = new() { comment = $"{changeType}ed {path}", changes = new Change[] { new Change() { changeType = changeType, item = new Item() { path = path, }, newContent = new Newcontent() { content = content, contentType = "rawtext" } } } }; requestBody.commits = new Commit[] { commit }; string requestJson = JsonSerializer.Serialize(requestBody); return new StringContent(requestJson, Encoding.ASCII, "application/json"); } public static T ReadJson<T>(string filename) { if (System.IO.File.Exists(filename)) { string jsonString = System.IO.File.ReadAllText(filename); T deserializedObj = JsonSerializer.Deserialize<T>(jsonString); return deserializedObj; } else { throw new DirectoryNotFoundException(); } } public static string GenerateId<T>(T model) { string id; if (model.GetType() == typeof(LandscapeModel)) { LandscapeModel landscape = (LandscapeModel)Convert.ChangeType(model, typeof(LandscapeModel)); id = (landscape.environment + "-" + MapRegion(landscape.location) + "-" + landscape.network_logical_name + "-infrastructure").ToUpper(); } else if (model.GetType() == typeof(SystemModel)) { SystemModel system = (SystemModel)Convert.ChangeType(model, typeof(SystemModel)); id = (system.environment + "-" + MapRegion(system.location) + "-" + system.network_logical_name + "-" + system.sid).ToUpper(); } else { throw new Exception("Object provided is neither a system nor a workload zone"); } return id; } public static string MapRegion(string region) { if (region == null) return ""; if (regionMapping.ContainsKey(region)) { return regionMapping[region]; } else { throw new KeyNotFoundException("location is not a valid Azure region"); } } public static async Task<byte[]> ProcessFormFile(IFormFile formFile, ModelStateDictionary modelState, string[] permittedExtensions, long sizeLimit) { var fieldDisplayName = ""; // Use reflection to obtain the display name for the model // property associated with this IFormFile. If a display // name isn't found, error messages simply won't show // a display name. MemberInfo property = typeof(FileUploadModel).GetProperty( formFile.Name[(formFile.Name.IndexOf(".", StringComparison.Ordinal) + 1)..]); if (property != null) { if (property.GetCustomAttribute(typeof(DisplayAttribute)) is DisplayAttribute displayAttribute) { fieldDisplayName = $"{displayAttribute.Name} "; } } // Don't trust the file name sent by the client. To display // the file name, HTML-encode the value. var trustedFileNameForDisplay = WebUtility.HtmlEncode(formFile.FileName); // Check the file length. This check doesn't catch files that only have // a BOM as their content. if (formFile.Length == 0) { modelState.AddModelError(formFile.Name, $"{fieldDisplayName}({trustedFileNameForDisplay}) is empty."); return Array.Empty<byte>(); } if (formFile.Length > sizeLimit) { var megabyteSizeLimit = sizeLimit / 1048576; modelState.AddModelError(formFile.Name, $"{fieldDisplayName}({trustedFileNameForDisplay}) exceeds " + $"{megabyteSizeLimit:N1} MB."); return Array.Empty<byte>(); } Regex rx = new(@"^\w{0,5}-\w{4}-\w{0,7}-\w{0,15}\.tfvars$"); if (!rx.IsMatch(formFile.FileName)) { modelState.AddModelError(formFile.Name, $"{fieldDisplayName}({trustedFileNameForDisplay}) is named incorrectly"); return Array.Empty<byte>(); } try { using var memoryStream = new MemoryStream(); await formFile.CopyToAsync(memoryStream); // Check the content length in case the file's only // content was a BOM and the content is actually // empty after removing the BOM. if (memoryStream.Length == 0) { modelState.AddModelError(formFile.Name, $"{fieldDisplayName}({trustedFileNameForDisplay}) is empty."); } if (!IsValidFileExtension(formFile.FileName, memoryStream, permittedExtensions)) { modelState.AddModelError(formFile.Name, $"{fieldDisplayName}({trustedFileNameForDisplay}) file type isn't permitted."); } else { return memoryStream.ToArray(); } } catch (Exception ex) { modelState.AddModelError(formFile.Name, $"{fieldDisplayName}({trustedFileNameForDisplay}) upload failed. " + $"Please contact the Help Desk for support. Error: {ex.HResult}"); } return Array.Empty<byte>(); } private static bool IsValidFileExtension(string fileName, Stream data, string[] permittedExtensions) { if (string.IsNullOrEmpty(fileName) || data == null || data.Length == 0) { return false; } var ext = Path.GetExtension(fileName).ToLowerInvariant(); if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext)) { return false; } return true; } public static string TfvarToJson(string hclString) { StringReader stringReader = new(hclString); StringBuilder jsonString = new(); jsonString.AppendLine("{"); while (true) { string currLine = stringReader.ReadLine(); if (currLine == null) { jsonString.Remove(jsonString.Length - 3, 1); jsonString.AppendLine("}"); break; } else if (currLine.StartsWith("#") || currLine == "") { continue; } else if (currLine.StartsWith("}")) { jsonString.Remove(jsonString.Length - 3, 1); jsonString.AppendLine("},"); } else { int equalIndex = currLine.IndexOf("="); if (equalIndex >= 0) { string key = currLine[..equalIndex].Trim(); if (!key.StartsWith("\"")) { key = "\"" + key + "\""; } string value = null; Console.WriteLine(key); if (key.EndsWith("tags\"")) { value += "["; currLine = stringReader.ReadLine(); while (!currLine.StartsWith("}")) { equalIndex = currLine.IndexOf("="); var tagKey = currLine[..equalIndex].Trim(); if (!tagKey.StartsWith("\"")) { tagKey = "\"" + tagKey + "\""; } var tagValue = currLine[(equalIndex + 1)..].Trim(); value += "{"; value += "\"Key\":" + tagKey + "," + "\"Value\":" + tagValue.Trim(','); value += "},"; currLine = stringReader.ReadLine().TrimStart(); } value = value.Trim(','); value += "],"; } else if (key.EndsWith("configuration_settings\"")) { value += "["; currLine = stringReader.ReadLine(); while (!currLine.StartsWith("}")) { equalIndex = currLine.IndexOf("="); var tagKey = currLine[..equalIndex].Trim(); if (!tagKey.StartsWith("\"")) { tagKey = "\"" + tagKey + "\""; } var tagValue = currLine[(equalIndex + 1)..].Trim(); value += "{"; value += "\"Key\":" + tagKey + "," + "\"Value\":" + tagValue.Trim(','); value += "},"; currLine = stringReader.ReadLine(); } value = value.Trim(','); value += "],"; } else { value = currLine[(equalIndex + 1)..].Trim(); if (!value.EndsWith(",") && !value.EndsWith("{")) { value += ","; } } if (value != null) jsonString.AppendLine(key + ":" + value); } } } return jsonString.ToString(); } public static async Task<AppFile> GetImagesFile(ITableStorageService<AppFile> appFileService) { string filename = "VM-Images.json"; string partitionKey = "VM"; AppFile file = null; try { file = await appFileService.GetByIdAsync(filename, partitionKey); if (file == null) throw new KeyNotFoundException(); } catch { byte[] byteContent = System.IO.File.ReadAllBytes("ParameterDetails/" + filename); using MemoryStream memory = new(byteContent); file = new AppFile() { Id = WebUtility.HtmlEncode(filename), Content = byteContent, UntrustedName = filename, Size = memory.Length, UploadDT = DateTime.UtcNow }; } return file ?? new AppFile(); } public static async Task<ImageDropdown[]> GetOfferedImages(ITableStorageService<AppFile> appFileService) { try { AppFile file = await GetImagesFile(appFileService); byte[] bytes = file.Content; string jsonString = Encoding.UTF8.GetString(bytes); ImageDropdown[] images = System.Text.Json.JsonSerializer.Deserialize<ImageDropdown[]>(jsonString); return images; } catch { return Array.Empty<ImageDropdown>(); } } } }