Webapp/SDAF/Controllers/LandscapeController.cs (470 lines of code) (raw):
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Configuration;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json;
using SDAFWebApp.Models;
using SDAFWebApp.Services;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace SDAFWebApp.Controllers
{
public class LandscapeController : Controller
{
private readonly ITableStorageService<LandscapeEntity> _landscapeService;
private readonly ITableStorageService<AppFile> _appFileService;
private FormViewModel<LandscapeModel> landscapeView;
private readonly IConfiguration _configuration;
private RestHelper restHelper;
private readonly ImageDropdown[] imagesOffered;
private List<SelectListItem> imageOptions;
private Dictionary<string, Image> imageMapping;
private readonly string sdafControlPlaneEnvironment;
private readonly string sdafControlPlaneLocation;
public LandscapeController(ITableStorageService<LandscapeEntity> landscapeService, ITableStorageService<AppFile> appFileService, IConfiguration configuration)
{
_landscapeService = landscapeService;
_appFileService = appFileService;
_configuration = configuration;
restHelper = new RestHelper(configuration);
landscapeView = SetViewData();
imagesOffered = Helper.GetOfferedImages(_appFileService).Result;
InitializeImageOptionsAndMapping();
sdafControlPlaneEnvironment = configuration["CONTROLPLANE_ENV"];
sdafControlPlaneLocation = configuration["CONTROLPLANE_LOC"];
}
private FormViewModel<LandscapeModel> SetViewData()
{
landscapeView = new FormViewModel<LandscapeModel>
{
SapObject = new LandscapeModel()
};
try
{
Grouping[] parameterArray = Helper.ReadJson<Grouping[]>("ParameterDetails/LandscapeDetails.json");
landscapeView.ParameterGroupings = parameterArray;
}
catch
{
landscapeView.ParameterGroupings = Array.Empty<Grouping>();
}
return landscapeView;
}
[ActionName("Index")]
public async Task<IActionResult> Index()
{
SapObjectIndexModel<LandscapeModel> landscapeIndex = new();
try
{
List<LandscapeEntity> landscapeEntities = await _landscapeService.GetAllAsync();
List<LandscapeModel> landscapes = landscapeEntities.FindAll(l => l.Landscape != null).ConvertAll(l => JsonConvert.DeserializeObject<LandscapeModel>(l.Landscape));
landscapeIndex.SapObjects = landscapes;
List<AppFile> appfiles = await _appFileService.GetAllAsync();
landscapeIndex.AppFiles = appfiles.FindAll(file => file.Id.EndsWith("INFRASTRUCTURE.tfvars"));
}
catch (Exception e)
{
TempData["error"] = "Error retrieving existing workload zones: " + e.Message;
}
return View(landscapeIndex);
}
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 async Task<ActionResult> GetWorkloadZones()
{
List<SelectListItem> options =
[
new SelectListItem { Text = "", Value = "" }
];
try
{
List<LandscapeEntity> landscapeEntities = await _landscapeService.GetAllAsync();
foreach (LandscapeEntity e in landscapeEntities)
{
options.Add(new SelectListItem
{
Text = e.RowKey,
Value = e.RowKey
});
}
}
catch
{
return null;
}
return Json(options);
}
[HttpGet]
public async Task<LandscapeModel> GetById(string id, string partitionKey)
{
if (id == null || partitionKey == null) throw new ArgumentNullException();
var landscapeEntity = await _landscapeService.GetByIdAsync(id, partitionKey);
if (landscapeEntity == null || landscapeEntity.Landscape == null) throw new KeyNotFoundException();
return JsonConvert.DeserializeObject<LandscapeModel>(landscapeEntity.Landscape);
}
// Format correctly for javascript consumption
[HttpGet]
public async Task<ActionResult> GetByIdJson(string id)
{
string environment = id[..id.IndexOf('-')];
LandscapeEntity landscape = await _landscapeService.GetByIdAsync(id, environment);
if (landscape == null || landscape.Landscape == null) return NotFound();
return Json(landscape.Landscape);
}
[HttpGet]
public async Task<LandscapeModel> GetDefault()
{
LandscapeEntity defaultLandscape = await _landscapeService.GetDefault();
if (defaultLandscape == null || defaultLandscape.Landscape == null) return null;
return JsonConvert.DeserializeObject<LandscapeModel>(defaultLandscape.Landscape);
}
[HttpGet]
public ActionResult GetDefaultJson()
{
LandscapeEntity landscapeEntity = _landscapeService.GetDefault().Result;
if (landscapeEntity == null) return NotFound();
return Json(landscapeEntity.Landscape);
}
[ActionName("Create")]
public IActionResult Create()
{
ViewBag.ValidImageOptions = (imagesOffered.Length != 0);
ViewBag.ImageOptions = imageOptions;
return View(landscapeView);
}
[HttpPost]
[ActionName("Create")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> CreateAsync(LandscapeModel landscape)
{
if (ModelState.IsValid || landscape.IsDefault)
{
try
{
if (landscape.IsDefault)
{
await UnsetDefault(landscape.Id);
}
landscape.Id = Helper.GenerateId(landscape);
DateTime currentDateAndTime = DateTime.Now;
landscape.LastModified = currentDateAndTime.ToShortDateString();
landscape.subscription_id = landscape.subscription.Replace("/subscriptions/", "");
await _landscapeService.CreateAsync(new LandscapeEntity(landscape));
TempData["success"] = "Successfully created workload zone " + landscape.Id;
string id = landscape.Id;
string path = $"/LANDSCAPE/{id}/{id}.tfvars";
string content = Helper.ConvertToTerraform(landscape);
return RedirectToAction("Index");
}
catch (Exception e)
{
ModelState.AddModelError("LandscapeId", "Error creating workload zone: " + e.Message);
}
}
landscapeView.SapObject = landscape;
ViewBag.ValidImageOptions = (imagesOffered.Length != 0);
ViewBag.ImageOptions = imageOptions;
return View(landscapeView);
}
[ActionName("Deploy")]
public async Task<IActionResult> DeployAsync(string id, string partitionKey)
{
try
{
LandscapeModel landscape = await GetById(id, partitionKey);
landscape.controlPlaneEnvironment = sdafControlPlaneEnvironment;
landscape.controlPlaneLocation = sdafControlPlaneLocation;
landscapeView.SapObject = landscape;
List<SelectListItem> environments = restHelper.GetEnvironmentsList().Result;
ViewBag.Environments = environments;
return View(landscapeView);
}
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
{
LandscapeModel landscape = await GetById(id, partitionKey);
string path = $"/LANDSCAPE/{id}/{id}.tfvars";
landscape.subscription_id = landscape.subscription.Replace("/subscriptions/", "");
string content = Helper.ConvertToTerraform(landscape);
await restHelper.UpdateRepo(path, content);
string pipelineId = _configuration["WORKLOADZONE_PIPELINE_ID"];
string branch = _configuration["SourceBranch"];
parameters.workload_zone = 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 workload zone deployment pipeline for " + id;
}
catch (Exception e)
{
TempData["error"] = "Error deploying workload zone " + id + ": " + e.Message;
}
return RedirectToAction("Index");
}
[ActionName("Delete")]
public async Task<IActionResult> DeleteAsync(string id, string partitionKey)
{
if (id == null)
{
return BadRequest();
}
LandscapeModel landscape = await GetById(id, partitionKey);
landscapeView.SapObject = landscape;
if (landscape == null)
{
return NotFound();
}
return View(landscapeView);
}
[HttpPost]
[ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmedAsync(string id, string partitionKey)
{
await _landscapeService.DeleteAsync(id, partitionKey);
TempData["success"] = "Successfully deleted workload zone " + id;
return RedirectToAction("Index");
}
[ActionName("Edit")]
public async Task<IActionResult> EditAsync(string id, string partitionKey)
{
try
{
ActionResult<LandscapeModel> result = await GetById(id, partitionKey);
LandscapeModel landscape = result.Value;
landscapeView.SapObject = landscape;
ViewBag.ValidImageOptions = (imagesOffered.Length != 0);
ViewBag.ImageOptions = imageOptions;
return View(landscapeView);
}
catch (Exception e)
{
TempData["error"] = e.Message;
return RedirectToAction("Index");
}
}
[HttpPost]
[ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditAsync(LandscapeModel landscape)
{
if (ModelState.IsValid)
{
try
{
string newId = Helper.GenerateId(landscape);
if (landscape.Id == null) landscape.Id = newId;
if (newId != landscape.Id)
{
landscape.Id = newId;
await SubmitNewAsync(landscape);
string id = landscape.Id;
string path = $"/LANDSCAPE/{id}/{id}.tfvars";
string content = Helper.ConvertToTerraform(landscape);
byte[] bytes = Encoding.UTF8.GetBytes(content);
AppFile file = new()
{
Id = WebUtility.HtmlEncode(path),
Content = bytes,
UntrustedName = path,
Size = bytes.Length,
UploadDT = DateTime.UtcNow
};
await _landscapeService.CreateTFVarsAsync(file);
return RedirectToAction("Edit", "Landscape", new { @id = landscape.Id, @partitionKey = landscape.environment }); //RedirectToAction("Index");
}
else
{
if (landscape.IsDefault)
{
await UnsetDefault(landscape.Id);
}
DateTime currentDateAndTime = DateTime.Now;
landscape.LastModified = currentDateAndTime.ToShortDateString();
await _landscapeService.UpdateAsync(new LandscapeEntity(landscape));
TempData["success"] = "Successfully updated workload zone " + landscape.Id;
string id = landscape.Id;
string path = $"/LANDSCAPE/{id}/{id}.tfvars";
string content = Helper.ConvertToTerraform(landscape);
byte[] bytes = Encoding.UTF8.GetBytes(content);
AppFile file = new()
{
Id = WebUtility.HtmlEncode(path),
Content = bytes,
UntrustedName = path,
Size = bytes.Length,
UploadDT = DateTime.UtcNow
};
await _landscapeService.CreateTFVarsAsync(file);
return RedirectToAction("Edit", "Landscape", new { @id = landscape.Id, @partitionKey = landscape.environment }); //RedirectToAction("Index");
}
}
catch (Exception e)
{
ModelState.AddModelError("LandscapeId", "Error editing workload zone: " + e.Message);
}
}
landscapeView.SapObject = landscape;
ViewBag.ValidImageOptions = (imagesOffered.Length != 0);
ViewBag.ImageOptions = imageOptions;
return View(landscapeView);
}
[HttpPost]
[ActionName("SubmitNew")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SubmitNewAsync(LandscapeModel landscape)
{
if (ModelState.IsValid)
{
try
{
if (landscape.IsDefault)
{
await UnsetDefault(landscape.Id);
}
landscape.Id = Helper.GenerateId(landscape);
DateTime currentDateAndTime = DateTime.Now;
landscape.LastModified = currentDateAndTime.ToShortDateString();
await _landscapeService.CreateAsync(new LandscapeEntity(landscape));
TempData["success"] = "Successfully created workload zone " + landscape.Id;
string id = landscape.Id;
string path = $"/LANDSCAPE/{id}/{id}.tfvars";
string content = Helper.ConvertToTerraform(landscape);
byte[] bytes = Encoding.UTF8.GetBytes(content);
AppFile file = new()
{
Id = WebUtility.HtmlEncode(id),
Content = bytes,
UntrustedName = id,
Size = bytes.Length,
UploadDT = DateTime.UtcNow
};
await _landscapeService.CreateTFVarsAsync(file);
return RedirectToAction("Index");
}
catch (Exception e)
{
ModelState.AddModelError("LandscapeId", "Error creating workload zone: " + e.Message);
}
}
landscapeView.SapObject = landscape;
ViewBag.ValidImageOptions = (imagesOffered.Length != 0);
ViewBag.ImageOptions = imageOptions;
return View("Edit", landscapeView);
}
[ActionName("Details")]
public async Task<IActionResult> DetailsAsync(string id, string partitionKey)
{
try
{
ActionResult<LandscapeModel> result = await GetById(id, partitionKey);
LandscapeModel landscape = result.Value;
landscapeView.SapObject = landscape;
return View(landscapeView);
}
catch (Exception e)
{
TempData["error"] = e.Message;
return RedirectToAction("Index");
}
}
[ActionName("Download")]
public ActionResult DownloadFile(string id, string partitionKey)
{
try
{
LandscapeModel landscape = GetById(id, partitionKey).Result;
string path = $"{id}.tfvars";
string content = Helper.ConvertToTerraform(landscape);
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
{
await UnsetDefault(id);
ActionResult<LandscapeModel> result = await GetById(id, partitionKey);
LandscapeModel landscape = result.Value;
landscape.IsDefault = true;
LandscapeEntity landscapeEntity = new(landscape);
await _landscapeService.UpdateAsync(landscapeEntity);
TempData["success"] = id + " is now the default workload zone";
}
catch (Exception e)
{
TempData["error"] = "Error setting default for workload zone: " + e.Message;
}
return RedirectToAction("Index");
}
public async Task UnsetDefault(string id)
{
try
{
LandscapeModel existingDefault = await GetDefault();
if (existingDefault != null && existingDefault.Id != id)
{
existingDefault.IsDefault = false;
await _landscapeService.UpdateAsync(new LandscapeEntity(existingDefault));
Console.WriteLine("Unset existing default " + existingDefault.Id);
}
}
catch (Exception e)
{
throw new Exception("Error unsetting the current default object: " + e.Message);
}
}
}
}