Merged PR 28923: - Added deployments

- Added deployments
- Cleanup up code to generic responses and http methods
- Added ansiconsole

Related work items: #69630, #69856
This commit is contained in:
Jurjen Ladenius
2022-04-21 09:32:22 +00:00
18 changed files with 292 additions and 103 deletions

View File

@@ -15,6 +15,7 @@
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="5.2.9" />
<PackageReference Include="Spectre.Console" Version="0.44.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,7 @@
namespace AzureRestApi.Models.Api
{
public class ApiResponse<T>
{
public List<T>? value { get; set; }
}
}

View File

@@ -1,9 +0,0 @@
using AzureRestApi.Models.Resources;
namespace AzureRestApi.Models.Api
{
public class ResourcesResponse
{
public List<Resource>? value { get; set; }
}
}

View File

@@ -1,9 +0,0 @@
using AzureRestApi.Models.Resources;
namespace AzureRestApi.Models.Api
{
public class SubscriptionResponse
{
public List<Subscription>? value { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
namespace AzureRestApi.Models.Resources
{
public class Deployment
{
public string? id { get; set; }
public string? name { get; set; }
public string? type { get; set; }
public string? location { get; set; }
public DeploymentProperties? properties { get; set; }
}
}

View File

@@ -0,0 +1,17 @@
namespace AzureRestApi.Models.Resources
{
public class DeploymentProperties
{
public string? templateHash { get; set; }
public string? mode { get; set; }
//public string? parameters { get; set; }
public string? provisioningState { get; set; }
public DateTimeOffset? timestamp { get; set; }
public string? duration { get; set; }
public string? correlationId { get; set; }
// public string? providers { get; set; }
//public string? dependencies { get; set; }
//public string? outputs { get; set; }
public List<Resource>? outputResources { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
namespace AzureRestApi.Models.Resources
{
public class ResourceGroup
{
public string? id { get; set; }
public string? name { get; set; }
public string? managedBy { get; set; }
public string? location { get; set; }
public Dictionary<string, string> tags { get; set; } = new Dictionary<string, string>();
public DeploymentProperties? properties { get; set; }
}
}

View File

@@ -0,0 +1,7 @@
namespace AzureRestApi.Models.Resources
{
public class ResourceGroupProperties
{
public string? provisioningState { get; set; }
}
}

View File

@@ -2,7 +2,7 @@
{
public class Settings
{
public string KeyVaultName { get; set; }
public string AzureTenantId { get; set; }
public string KeyVaultName { get; set; } = string.Empty;
public string AzureTenantId { get; set; } = string.Empty;
}
}

View File

@@ -27,10 +27,13 @@ namespace AzureRestApi
services.AddSingleton(settings);
services.AddHttpClient<AzureSubscriptionRepository>();
services.AddHttpClient<AzureDeploymentRepository>();
services.AddHttpClient<AzureResourceRepository>();
services.AddHttpClient<AzureTagRepository>();
services.AddHttpClient<AzureResourceGroupRepository>();
services.AddScoped<CreatedOnDateService>();
services.AddScoped<DeploymentTypeService>();
services.AddHostedService<OptionService>();
} );

View File

@@ -0,0 +1,44 @@
using AzureRestApi.Models.Api;
using System.Net.Http.Headers;
using System.Text.Json;
namespace AzureRestApi.Repositories
{
public abstract class AzureBaseRepository<T>
{
private readonly HttpClient _httpClient;
private readonly AccessTokenRepository _accessTokenRepository;
public AzureBaseRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository)
{
_httpClient = httpClient;
_accessTokenRepository = accessTokenRepository;
}
protected async Task<List<T>> GetAllByUri(Uri uri)
{
var accessToken = await _accessTokenRepository.GetAccessToken();
HttpRequestMessage request = new(HttpMethod.Get, uri);
request.Headers.Accept.Clear();
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await _httpClient.SendAsync(request).ConfigureAwait(false);
try
{
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var result = JsonSerializer.Deserialize<ApiResponse<T>>(responseString)?.value;
return result ?? new List<T>();
}
catch
{
return new List<T>();
}
}
}
}

View File

@@ -0,0 +1,25 @@
using AzureRestApi.Models.Resources;
namespace AzureRestApi.Repositories
{
public class AzureDeploymentRepository : AzureBaseRepository<Deployment>
{
public AzureDeploymentRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository) : base(httpClient, accessTokenRepository)
{
}
public async Task<List<Deployment>> GetAllBySubscription(string subscriptionId)
{
// GET https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.Resources/deployments/?api-version=2021-04-01
var uri = new Uri($"https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.Resources/deployments/?api-version=2021-04-01");
return await GetAllByUri(uri);
}
public async Task<List<Deployment>> GetAllByResourceGroup(string subscriptionId, string resourceGroupName)
{
// GET https://management.azure.com/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/Microsoft.Resources/deployments/?api-version=2021-04-01
var uri = new Uri($"https://management.azure.com/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/Microsoft.Resources/deployments/?api-version=2021-04-01");
return await GetAllByUri(uri);
}
}
}

View File

@@ -0,0 +1,18 @@
using AzureRestApi.Models.Resources;
namespace AzureRestApi.Repositories
{
public class AzureResourceGroupRepository : AzureBaseRepository<ResourceGroup>
{
public AzureResourceGroupRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository) : base(httpClient, accessTokenRepository)
{
}
public async Task<List<ResourceGroup>> GetAll(string subscriptionId)
{
// GET https://management.azure.com/subscriptions/{subscriptionId}/resourcegroups?api-version=2021-04-01
var uri = new Uri($"https://management.azure.com/subscriptions/{subscriptionId}/resourcegroups?api-version=2021-04-01");
return await GetAllByUri(uri);
}
}
}

View File

@@ -5,34 +5,17 @@ using System.Text.Json;
namespace AzureRestApi.Repositories
{
public class AzureResourceRepository
public class AzureResourceRepository : AzureBaseRepository<Resource>
{
private readonly HttpClient _httpClient;
private readonly AccessTokenRepository _accessTokenRepository;
public AzureResourceRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository)
public AzureResourceRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository) : base(httpClient, accessTokenRepository)
{
_httpClient = httpClient;
_accessTokenRepository = accessTokenRepository;
}
public async Task<List<Resource>?> GetAllResources(string subscriptionId)
public async Task<List<Resource>> GetAll(string subscriptionId)
{
// GET https://management.azure.com/subscriptions/{subscriptionId}/resources?$filter={$filter}&$expand={$expand}&$top={$top}&api-version=2021-04-01
var accessToken = await _accessTokenRepository.GetAccessToken();
var uri = new Uri("https://management.azure.com/subscriptions/" + subscriptionId + "/resources?$expand=createdTime,changedTime&api-version=2021-04-01");
HttpRequestMessage request = new(HttpMethod.Get, uri);
request.Headers.Accept.Clear();
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await _httpClient.SendAsync(request).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
return JsonSerializer.Deserialize<ResourcesResponse>(responseString)?.value;
var uri = new Uri($"https://management.azure.com/subscriptions/{subscriptionId}/resources?$expand=createdTime,changedTime&api-version=2021-04-01");
return await GetAllByUri(uri);
}
}
}

View File

@@ -16,7 +16,7 @@ namespace AzureRestApi.Repositories
_accessTokenRepository = accessTokenRepository;
}
public async Task<List<Subscription>?> GetAllSubscriptions()
public async Task<List<Subscription>?> GetAll()
{
// GET https://management.azure.com/subscriptions?api-version=2020-01-01
@@ -33,7 +33,7 @@ namespace AzureRestApi.Repositories
var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
return JsonSerializer.Deserialize<SubscriptionResponse>(responseString)?.value;
return JsonSerializer.Deserialize< ApiResponse<Subscription>>(responseString)?.value;
}
}
}

View File

@@ -1,4 +1,5 @@
using AzureRestApi.Repositories;
using Spectre.Console;
namespace AzureRestApi.Services
{
@@ -18,23 +19,31 @@ namespace AzureRestApi.Services
public async Task SetCreatedOnDateTags(bool skipDone = true)
{
var defaultForeGround = Console.ForegroundColor;
Console.WriteLine("==============================================");
Console.WriteLine("Setting CreatedOnDateTags");
Console.WriteLine("==============================================");
var rule = new Rule("[skyblue1]Setting CreatedOnDateTags[/]");
rule.Alignment = Justify.Left;
rule.Style = Style.Parse("skyblue1");
AnsiConsole.Write(rule);
var subscriptions = await _azureSubscriptionRepository.GetAllSubscriptions();
await AnsiConsole.Status()
.AutoRefresh(true)
.Spinner(Spinner.Known.Default)
.StartAsync("Retrieving subscriptions...", async ctx =>
{
var subscriptions = await _azureSubscriptionRepository.GetAll();
if (subscriptions == null || !subscriptions.Any()) throw new Exception("No subscriptions found");
subscriptions = subscriptions.Where(s => s.Enabled && !string.IsNullOrWhiteSpace(s.subscriptionId)).ToList();
if (!subscriptions.Any()) throw new Exception("No subscriptions found");
foreach (var subscription in subscriptions)
{
Console.WriteLine("----------------------------------------------");
Console.WriteLine(subscription.displayName);
Console.WriteLine("----------------------------------------------");
rule = new Rule($"[skyblue1]{subscription.displayName}[/]");
rule.Alignment = Justify.Left;
rule.Style = Style.Parse("skyblue1 dim");
AnsiConsole.Write(rule);
var resources = await _azureResourceRepository.GetAllResources(subscription.subscriptionId);
ctx.Status($"Getting the resources for subscription '{subscription.displayName}'");
var resources = await _azureResourceRepository.GetAll(subscription.subscriptionId);
if (resources == null || !resources.Any()) continue;
resources = resources.Where(r => !string.IsNullOrWhiteSpace(r.CreatedOn) && !string.IsNullOrWhiteSpace(r.id)).ToList();
@@ -43,19 +52,14 @@ namespace AzureRestApi.Services
resources = resources.Where(r => !r.tags.Any(t => t.Key == TagName && !string.IsNullOrWhiteSpace(t.Value))).ToList();
}
ctx.Status($"Setting created date tag on resources in subscription '{subscription.displayName}'");
foreach (var resource in resources)
{
Console.Write(resource.id);
var tagSuccess = await _azureTagRepository.SetTag(resource.id, TagName, resource.CreatedOn);
Console.ForegroundColor = tagSuccess ? ConsoleColor.DarkGreen : ConsoleColor.Red;
Console.Write(tagSuccess ? " V" : " X");
Console.ForegroundColor = defaultForeGround;
Console.WriteLine();
AnsiConsole.MarkupLine($"{resource.id} " + (tagSuccess ? "[green]V[/]" : "[red]X[/]"));
}
}
Console.WriteLine("==============================================");
});
}
}
}

View File

@@ -0,0 +1,73 @@
using AzureRestApi.Repositories;
using Spectre.Console;
namespace AzureRestApi.Services
{
public class DeploymentTypeService
{
private readonly AzureSubscriptionRepository _azureSubscriptionRepository;
private readonly AzureDeploymentRepository _azureDeploymentRepository;
private readonly AzureResourceGroupRepository _azureResourceGroupRepository;
public DeploymentTypeService(AzureSubscriptionRepository azureSubscriptionRepository, AzureDeploymentRepository azureDeploymentRepository, AzureResourceGroupRepository azureResourceGroupRepository)
{
_azureSubscriptionRepository = azureSubscriptionRepository;
_azureDeploymentRepository = azureDeploymentRepository;
_azureResourceGroupRepository = azureResourceGroupRepository;
}
public async Task CheckDeploymentTypes()
{
var rule = new Rule("[skyblue1]Checking deployment types[/]");
rule.Alignment = Justify.Left;
rule.Style = Style.Parse("skyblue1");
AnsiConsole.Write(rule);
await AnsiConsole.Status()
.AutoRefresh(true)
.Spinner(Spinner.Known.Default)
.StartAsync("Retrieving subscriptions...", async ctx =>
{
// Retrieve subscriptions
var subscriptions = await _azureSubscriptionRepository.GetAll();
if (subscriptions == null || !subscriptions.Any()) throw new Exception("No subscriptions found");
subscriptions = subscriptions.Where(s => s.Enabled && !string.IsNullOrWhiteSpace(s.subscriptionId)).ToList();
if (!subscriptions.Any()) throw new Exception("No subscriptions found");
foreach (var subscription in subscriptions)
{
rule = new Rule($"[skyblue1]{subscription.displayName}[/]");
rule.Alignment = Justify.Left;
rule.Style = Style.Parse("skyblue1 dim");
AnsiConsole.Write(rule);
ctx.Status($"Getting the deployments for subscription '{subscription.displayName}'");
var deployments = await _azureDeploymentRepository.GetAllBySubscription(subscription.subscriptionId);
if (deployments == null || !deployments.Any()) continue;
foreach (var deployment in deployments)
{
AnsiConsole.WriteLine(Markup.Escape(deployment?.name));
}
ctx.Status($"Getting the resource groups for subscription '{subscription.displayName}'");
var resourceGroups = await _azureResourceGroupRepository.GetAll(subscription.subscriptionId);
foreach (var resourceGroup in resourceGroups)
{
ctx.Status($"Getting the deployments for resourcegroup {resourceGroup.name} in subscription '{subscription.displayName}'");
var resourceGroupDeployments = await _azureDeploymentRepository.GetAllByResourceGroup(subscription.subscriptionId, resourceGroup.name);
foreach (var deployment in resourceGroupDeployments)
{
AnsiConsole.WriteLine(Markup.Escape($"{resourceGroup.name} - {deployment?.name}"));
}
}
}
});
}
}
}

View File

@@ -1,5 +1,6 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Spectre.Console;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -12,45 +13,46 @@ namespace AzureRestApi.Services
{
private readonly ILogger<OptionService> _logger;
private readonly CreatedOnDateService _createdOnDateService;
private readonly DeploymentTypeService _deploymentTypeService;
public OptionService(ILogger<OptionService> logger, CreatedOnDateService createdOnDateService)
public OptionService(ILogger<OptionService> logger, CreatedOnDateService createdOnDateService, DeploymentTypeService deploymentTypeService)
{
_logger = logger;
_createdOnDateService = createdOnDateService;
_deploymentTypeService = deploymentTypeService;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
int userInput = 0;
bool validatedInput = false;
while (!validatedInput)
{
Console.WriteLine("================================================================");
Console.WriteLine("Cloud Egineering Console App");
Console.WriteLine("================================================================");
var rule = new Rule("[yellow]Cloud Egineering Console App[/]");
AnsiConsole.Write(rule);
Console.WriteLine("-- This couldn't be done with Powershell, so here we are.... ");
Console.WriteLine();
Console.WriteLine("Select what you want to do:");
Console.WriteLine("1. Update missing CreatedOnDate tags.");
Console.WriteLine("0. Exit.");
string? result = Console.ReadLine();
bool parsed = int.TryParse(result, out userInput);
validatedInput = (parsed && (new[] { 1, 0 }).Contains(userInput));
if (!validatedInput)
var choices = new[]
{
Console.WriteLine("Please enter a valid choice");
}
}
"Update missing CreatedOnDate tags.",
"Update missing IaC Deployment type tags.",
"Exit"
};
var result = AnsiConsole.Prompt(new SelectionPrompt<string>()
.Title("Select what you want to do:")
.PageSize(10)
.MoreChoicesText("[grey](Move up and down to reveal more choices)[/]")
.AddChoices(choices));
if (userInput == 1)
if (result == choices[0])
{
await _createdOnDateService.SetCreatedOnDateTags(true);
}
else if (result == choices[1])
{
await _deploymentTypeService.CheckDeploymentTypes();
}
Console.WriteLine("Bye.");
rule = new Rule("[yellow]Done. Bye.[/]");
AnsiConsole.Write(rule);
}
public Task StopAsync(CancellationToken cancellationToken)