Webapp/SDAF/Controllers/SystemController.cs (552 lines of code) (raw):
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using SDAFWebApp.Models;
using SDAFWebApp.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace SDAFWebApp.Controllers
{
public class SystemController : Controller
{
private readonly ITableStorageService<SystemEntity> _systemService;
private readonly ITableStorageService<AppFile> _appFileService;
private FormViewModel<SystemModel> systemView;
private readonly IConfiguration _configuration;
private readonly RestHelper restHelper;
private ImageDropdown[] imagesOffered;
private List<SelectListItem> imageOptions;
private Dictionary<string, Image> imageMapping;
public SystemController(ITableStorageService<SystemEntity> systemService, ITableStorageService<AppFile> appFileService, IConfiguration configuration)
{
_systemService = systemService;
_appFileService = appFileService;
_configuration = configuration;
restHelper = new RestHelper(configuration);
systemView = SetViewData();
imagesOffered = Helper.GetOfferedImages(_appFileService).Result;
InitializeImageOptionsAndMapping();
}
private FormViewModel<SystemModel> SetViewData()
{
systemView = new FormViewModel<SystemModel>
{
SapObject = new SystemModel()
};
try
{
Grouping[] parameterArray = Helper.ReadJson<Grouping[]>("ParameterDetails/SystemDetails.json");
systemView.ParameterGroupings = parameterArray;
}
catch
{
systemView.ParameterGroupings = Array.Empty<Grouping>();
}
return systemView;
}
[ActionName("Index")]
public async Task<IActionResult> Index()
{
SapObjectIndexModel<SystemModel> systemIndex = new();
try
{
List<SystemEntity> systemEntities = await _systemService.GetAllAsync();
List<SystemModel> systems = systemEntities.FindAll(s => s.System != null).ConvertAll(s => JsonConvert.DeserializeObject<SystemModel>(s.System));
systemIndex.SapObjects = systems;
List<AppFile> appfiles = await _appFileService.GetAllAsync();
systemIndex.AppFiles = appfiles.FindAll(file => !file.Id.EndsWith("INFRASTRUCTURE.tfvars") && file.Id != "VM-Images.json" && file.Id.IndexOf("_custom_") == -1);
systemIndex.ImagesFile = await Helper.GetImagesFile(_appFileService);
}
catch (Exception e)
{
TempData["error"] = "Error retrieving existing systems: " + e.Message;
}
return View(systemIndex);
}
[HttpGet]
public async Task<SystemModel> GetById(string id, string partitionKey)
{
if (id == null || partitionKey == null) throw new ArgumentNullException();
var systemEntity = await _systemService.GetByIdAsync(id, partitionKey);
if (systemEntity == null || systemEntity.System == null) throw new KeyNotFoundException();
SystemModel s = null;
try
{
s = JsonConvert.DeserializeObject<SystemModel>(systemEntity.System);
}
catch
{
}
AppFile file = null;
try
{
file = await _appFileService.GetByIdAsync(id + "_custom_naming.json", partitionKey);
s.name_override_file = id + "_custom_naming.json";
}
catch
{
}
file = null;
try
{
file = await _appFileService.GetByIdAsync(id + "_custom_sizes.json", partitionKey);
s.custom_disk_sizes_filename = id + "_custom_sizes.json";
s.database_size = "Custom";
}
catch
{
}
return s;
}
[HttpGet]
public async Task<SystemModel> GetDefault()
{
SystemEntity defaultSystem = await _systemService.GetDefault();
if (defaultSystem == null || defaultSystem.System == null) return null;
return JsonConvert.DeserializeObject<SystemModel>(defaultSystem.System);
}
[HttpGet]
public async Task<ActionResult> GetDefaultJson()
{
SystemEntity systemEntity = await _systemService.GetDefault();
if (systemEntity == null) return NotFound();
return Json(systemEntity.System);
}
public void InitializeImageOptionsAndMapping()
{
imageMapping = [];
imageOptions =
[
new SelectListItem()
];
if (imagesOffered.Length > 0)
{
foreach (ImageDropdown imageDropdown in imagesOffered)
{
if (!imageMapping.ContainsKey(imageDropdown.name))
{
imageMapping.Add(imageDropdown.name, imageDropdown.data);
imageOptions.Add(new SelectListItem(imageDropdown.name, imageDropdown.name));
}
}
}
}
[HttpGet]
public ActionResult GetImage(string name)
{
if (name != null && imageMapping.ContainsKey(name))
{
Image image = imageMapping[name];
return Json(image);
}
else
{
throw new Exception();
}
}
[ActionName("Create")]
public IActionResult Create()
{
ViewBag.ValidImageOptions = (imagesOffered.Length != 0);
ViewBag.ImageOptions = imageOptions;
return View(systemView);
}
[HttpPost]
[ActionName("Create")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> CreateAsync(SystemModel system)
{
if (ModelState.IsValid)
{
try
{
if (system.IsDefault)
{
await UnsetDefault(system.Id);
}
system.Id = Helper.GenerateId(system);
DateTime currentDateAndTime = DateTime.Now;
system.LastModified = currentDateAndTime.ToShortDateString();
system.subscription_id = system.subscription.Replace("/subscriptions/","");
SystemEntity systemEntity = new(system);
await _systemService.CreateAsync(systemEntity);
TempData["success"] = "Successfully created system " + system.Id;
return RedirectToAction("Index");
}
catch (Exception e)
{
ModelState.AddModelError("SystemId", "Error creating system: " + e.Message);
}
}
systemView.SapObject = system;
ViewBag.ValidImageOptions = (imagesOffered.Length != 0);
ViewBag.ImageOptions = imageOptions;
return View(systemView);
}
[ActionName("Deploy")]
public async Task<IActionResult> DeployAsync(string id, string partitionKey)
{
try
{
SystemModel system = await GetById(id, partitionKey);
systemView.SapObject = system;
List<SelectListItem> environments = restHelper.GetEnvironmentsList().Result;
ViewBag.Environments = environments;
return View(systemView);
}
catch (Exception e)
{
TempData["error"] = e.Message;
return RedirectToAction("Index");
}
}
[HttpPost]
[ActionName("Deploy")]
public async Task<RedirectToActionResult> DeployConfirmedAsync(string id, string partitionKey, Templateparameters parameters)
{
try
{
SystemModel system = await GetById(id, partitionKey);
AppFile file = null;
try
{
file = await _appFileService.GetByIdAsync(id + "_custom_naming.json", partitionKey);
system.name_override_file = id + "_custom_naming.json";
var stream = new MemoryStream(file.Content);
string thisContent = System.Text.Encoding.UTF8.GetString(stream.ToArray());
string pathForNaming = $"/SYSTEM/{id}/{id}_custom_naming.json";
await restHelper.UpdateRepo(pathForNaming, thisContent);
}
catch
{
}
file = null;
try
{
file = await _appFileService.GetByIdAsync(id + "_custom_sizes.json", partitionKey);
var stream = new MemoryStream(file.Content);
system.custom_disk_sizes_filename = id + "_custom_sizes.json";
system.database_size = "Custom";
string thisContent = System.Text.Encoding.UTF8.GetString(stream.ToArray());
string pathForNaming = $"/SYSTEM/{id}/{id}_custom_sizes.json";
await restHelper.UpdateRepo(pathForNaming, thisContent);
}
catch
{
}
string path = $"/SYSTEM/{id}/{id}.tfvars";
system.subscription_id = system.subscription.Replace("/subscriptions/", "");
string content = Helper.ConvertToTerraform(system);
await restHelper.UpdateRepo(path, content);
string pipelineId = _configuration["SYSTEM_PIPELINE_ID"];
string branch = _configuration["SourceBranch"];
parameters.sap_system = id;
PipelineRequestBody requestBody = new()
{
resources = new Resources
{
repositories = new Repositories
{
self = new Self
{
refName = $"refs/heads/{branch}"
}
}
},
templateParameters = parameters
};
await restHelper.TriggerPipeline(pipelineId, requestBody);
TempData["success"] = "Successfully triggered system deployment pipeline for " + id;
}
catch (Exception e)
{
TempData["error"] = "Error deploying system " + id + ": " + e.Message;
}
return RedirectToAction("Index");
}
[ActionName("Install")]
public async Task<IActionResult> InstallAsync(string id, string partitionKey)
{
try
{
SystemModel system = await GetById(id, partitionKey);
systemView.SapObject = system;
List<SelectListItem> environments = restHelper.GetEnvironmentsList().Result;
ViewBag.Environments = environments;
return View(systemView);
}
catch (Exception e)
{
TempData["error"] = e.Message;
return RedirectToAction("Index");
}
}
[HttpPost]
[ActionName("Install")]
public async Task<IActionResult> InstallConfirmedAsync(string id, string partitionKey, Templateparameters parameters)
{
try
{
SystemModel system = await GetById(id, partitionKey);
string pipelineId = _configuration["SAP_INSTALL_PIPELINE_ID"];
string branch = _configuration["SourceBranch"];
PipelineRequestBody requestBody = new()
{
resources = new Resources
{
repositories = new Repositories
{
self = new Self
{
refName = $"refs/heads/{branch}"
}
}
},
templateParameters = parameters
};
await restHelper.TriggerPipeline(pipelineId, requestBody);
TempData["success"] = "Successfully triggered SAP installation pipeline for " + id;
}
catch (Exception e)
{
TempData["error"] = "Error triggering SAP installation pipeline for system " + id + ": " + e.Message;
}
return RedirectToAction("Index");
}
[ActionName("Delete")]
public async Task<IActionResult> DeleteAsync(string id, string partitionKey)
{
if (id == null)
{
return BadRequest();
}
SystemModel system = await GetById(id, partitionKey);
systemView.SapObject = system;
if (system == null)
{
return NotFound();
}
return View(systemView);
}
[HttpPost]
[ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmedAsync(string id, string partitionKey)
{
await _systemService.DeleteAsync(id, partitionKey);
TempData["success"] = "Successfully deleted system " + id;
return RedirectToAction("Index");
}
[ActionName("Edit")]
public async Task<IActionResult> EditAsync(string id, string partitionKey)
{
try
{
SystemModel system = await GetById(id, partitionKey);
systemView.SapObject = system;
ViewBag.ValidImageOptions = (imagesOffered.Length != 0);
ViewBag.ImageOptions = imageOptions;
return View(systemView);
}
catch (Exception e)
{
TempData["error"] = e.Message;
return RedirectToAction("Index");
}
}
[HttpPost]
[ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditAsync(SystemModel system)
{
if (ModelState.IsValid)
{
try
{
string newId = Helper.GenerateId(system);
if (system.Id == null) system.Id = newId;
if (newId != system.Id)
{
if (String.IsNullOrEmpty(system.Description))
{
if ((bool)system.database_high_availability || (bool)system.scs_high_availability)
{
system.Description = system.database_platform + " high availability system on " + system.scs_server_image.publisher + " " + system.scs_server_image.offer + " " + system.scs_server_image.sku;
}
else
{
system.Description = system.database_platform + " distributed system on " + system.scs_server_image.publisher + " " + system.scs_server_image.offer + " " + system.scs_server_image.sku;
}
}
system.subscription_id = system.subscription.Replace("/subscriptions/", "");
await SubmitNewAsync(system);
string id = system.Id;
string path = $"/SYSTEM/{id}/{id}.tfvars";
string content = Helper.ConvertToTerraform(system);
byte[] bytes = Encoding.UTF8.GetBytes(content);
AppFile file = new()
{
Id = WebUtility.HtmlEncode(path),
Content = bytes,
UntrustedName = path,
Size = bytes.Length,
UploadDT = DateTime.UtcNow
};
await _systemService.CreateTFVarsAsync(file);
return RedirectToAction("Edit", "System", new { @id = system.Id, @partitionKey = system.environment }); //RedirectToAction("Index");
}
else
{
if (system.IsDefault)
{
await UnsetDefault(system.Id);
}
if (String.IsNullOrEmpty(system.Description))
{
if ((bool)system.database_high_availability || (bool)system.scs_high_availability)
{
system.Description = system.database_platform + " high availability system on " + system.scs_server_image.publisher + " " + system.scs_server_image.offer + " " + system.scs_server_image.sku;
}
else
{
system.Description = system.database_platform + " distributed system on " + system.scs_server_image.publisher + " " + system.scs_server_image.offer + " " + system.scs_server_image.sku;
}
}
DateTime currentDateAndTime = DateTime.Now;
system.LastModified = currentDateAndTime.ToShortDateString();
await _systemService.UpdateAsync(new SystemEntity(system));
TempData["success"] = "Successfully updated system " + system.Id;
string id = system.Id;
string path = $"/SYSTEM/{id}/{id}.tfvars";
string content = Helper.ConvertToTerraform(system);
byte[] bytes = Encoding.UTF8.GetBytes(content);
AppFile file = new()
{
Id = WebUtility.HtmlEncode(path),
Content = bytes,
UntrustedName = path,
Size = bytes.Length,
UploadDT = DateTime.UtcNow
};
await _systemService.CreateTFVarsAsync(file);
return RedirectToAction("Edit", "System", new { @id = system.Id, @partitionKey = system.environment }); //RedirectToAction("Index");
}
}
catch (Exception e)
{
ModelState.AddModelError("SystemId", "Error editing system: " + e.Message);
}
}
systemView.SapObject = system;
ViewBag.ValidImageOptions = (imagesOffered.Length != 0);
ViewBag.ImageOptions = imageOptions;
return View(systemView);
}
[HttpPost]
[ActionName("SubmitNew")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SubmitNewAsync(SystemModel system)
{
if (ModelState.IsValid)
{
try
{
if (system.IsDefault)
{
await UnsetDefault(system.Id);
}
system.Id = Helper.GenerateId(system);
DateTime currentDateAndTime = DateTime.Now;
system.LastModified = currentDateAndTime.ToShortDateString();
await _systemService.CreateAsync(new SystemEntity(system));
TempData["success"] = "Successfully created system " + system.Id;
return RedirectToAction("Index");
}
catch (Exception e)
{
ModelState.AddModelError("SystemId", "Error creating system: " + e.Message);
}
}
systemView.SapObject = system;
ViewBag.ValidImageOptions = (imagesOffered.Length != 0);
ViewBag.ImageOptions = imageOptions;
return View("Edit", systemView);
}
[ActionName("Details")]
public async Task<IActionResult> DetailsAsync(string id, string partitionKey)
{
try
{
SystemModel system = await GetById(id, partitionKey);
systemView.SapObject = system;
return View(systemView);
}
catch (Exception e)
{
TempData["error"] = e.Message;
return RedirectToAction("Index");
}
}
[ActionName("Download")]
public ActionResult DownloadFile(string id, string partitionKey)
{
try
{
SystemModel system = GetById(id, partitionKey).Result;
string path = $"{id}.tfvars";
string content = Helper.ConvertToTerraform(system);
var stream = new MemoryStream(Encoding.UTF8.GetBytes(content));
return new FileStreamResult(stream, new MediaTypeHeaderValue("text/plain"))
{
FileDownloadName = path
};
}
catch (Exception e)
{
TempData["error"] = "Something went wrong downloading file " + id + ": " + e.Message;
return RedirectToAction("Index");
}
}
[ActionName("MakeDefault")]
public async Task<IActionResult> MakeDefault(string id, string partitionKey)
{
try
{
// Unset the existing default
await UnsetDefault(id);
// Update current system as default
SystemModel system = await GetById(id, partitionKey);
system.IsDefault = true;
SystemEntity systemEntity = new(system);
await _systemService.UpdateAsync(systemEntity);
}
catch (Exception e)
{
ModelState.AddModelError("SystemId", "Error setting default for system: " + e.Message);
}
return RedirectToAction("Index");
}
public async Task UnsetDefault(string id)
{
try
{
SystemModel existingDefault = await GetDefault();
if (existingDefault != null && existingDefault.Id != id)
{
existingDefault.IsDefault = false;
await _systemService.UpdateAsync(new SystemEntity(existingDefault));
Console.WriteLine("Unset existing default " + existingDefault.Id);
}
}
catch (Exception e)
{
throw new Exception("Error unsetting the current default object: " + e.Message);
}
}
}
}