Merged PR 63702: Add Sonar Client to update permissions and tags in Sonar Projects to new team structure

Add Sonar Client to update permissions and tags in Sonar Projects to new team structure

Related work items: #125680
This commit is contained in:
Johannes Oenema Effectory
2025-11-05 15:18:52 +00:00
parent e30af22220
commit 91980817e0
66 changed files with 1586 additions and 1296 deletions

View File

@@ -2,20 +2,20 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.6.0" />
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.3.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
<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" />
<PackageReference Include="Azure.Identity" Version="1.17.0" />
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.8.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.78.0" />
<PackageReference Include="Spectre.Console" Version="0.53.0" />
</ItemGroup>
<ItemGroup>

View File

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

View File

@@ -1,9 +1,8 @@
using AzureRestApi.Models.Resources;
namespace AzureRestApi.Models.Api
namespace AzureRestApi.Models.Api;
public class TagProperties
{
public class TagProperties
{
public TagTags properties { get; set; } = new TagTags();
}
}
public TagTags properties { get; set; } = new TagTags();
}

View File

@@ -1,11 +1,10 @@
namespace AzureRestApi.Models.Resources
namespace AzureRestApi.Models.Resources;
public class Deployment
{
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; }
}
}
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

@@ -1,17 +1,16 @@
namespace AzureRestApi.Models.Resources
namespace AzureRestApi.Models.Resources;
public class DeploymentProperties
{
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; }
}
}
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

@@ -1,28 +1,27 @@
namespace AzureRestApi.Models.Resources
{
public class Resource
{
public string? id { get; set; }
public string? name { get; set; }
public string? type { get; set; }
public string? location { get; set; }
public string? createdTime { get; set; }
public string? changedTime { get; set; }
public Dictionary<string, string> tags { get; set; } = new Dictionary<string, string>();
namespace AzureRestApi.Models.Resources;
public string? CreatedOn
public class Resource
{
public string? id { get; set; }
public string? name { get; set; }
public string? type { get; set; }
public string? location { get; set; }
public string? createdTime { get; set; }
public string? changedTime { get; set; }
public Dictionary<string, string> tags { get; set; } = new Dictionary<string, string>();
public string? CreatedOn
{
get
{
get
if (string.IsNullOrWhiteSpace(createdTime))
{
if (string.IsNullOrWhiteSpace(createdTime))
{
return changedTime;
}
else
{
return createdTime;
}
return changedTime;
}
else
{
return createdTime;
}
}
}
}
}

View File

@@ -1,12 +1,11 @@
namespace AzureRestApi.Models.Resources
namespace AzureRestApi.Models.Resources;
public class ResourceGroup
{
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; }
}
}
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

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

View File

@@ -1,20 +1,19 @@
namespace AzureRestApi.Models.Resources
{
public class Subscription
{
public string? id { get; set; }
public string? authorizationSource { get; set; }
public string? subscriptionId { get; set; }
public string? tenantId { get; set; }
public string? displayName { get; set; }
public string? state { get; set; }
namespace AzureRestApi.Models.Resources;
public bool Enabled
public class Subscription
{
public string? id { get; set; }
public string? authorizationSource { get; set; }
public string? subscriptionId { get; set; }
public string? tenantId { get; set; }
public string? displayName { get; set; }
public string? state { get; set; }
public bool Enabled
{
get
{
get
{
return state == "Enabled";
}
return state == "Enabled";
}
}
}
}

View File

@@ -1,7 +1,6 @@
namespace AzureRestApi.Models.Resources
namespace AzureRestApi.Models.Resources;
public class TagTags
{
public class TagTags
{
public Dictionary<string, string> tags { get; set; } = new Dictionary<string, string>();
}
}
public Dictionary<string, string> tags { get; set; } = new Dictionary<string, string>();
}

View File

@@ -1,8 +1,7 @@
namespace AzureRestApi.Models
namespace AzureRestApi.Models;
public class Settings
{
public class Settings
{
public string KeyVaultName { get; set; } = string.Empty;
public string AzureTenantId { get; set; } = string.Empty;
}
}
public string KeyVaultName { get; set; } = string.Empty;
public string AzureTenantId { get; set; } = string.Empty;
}

View File

@@ -5,37 +5,36 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace AzureRestApi
namespace AzureRestApi;
class Program
{
class Program
{
static Task Main(string[] args) =>
CreateHostBuilder(args).Build().RunAsync();
static Task Main(string[] args) =>
CreateHostBuilder(args).Build().RunAsync();
static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((builder, services) =>
{
IConfiguration config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables()
.Build();
static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((_, services) =>
{
IConfiguration config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables()
.Build();
Settings settings = config.GetRequiredSection("Settings").Get<Settings>();
Settings settings = config.GetRequiredSection("Settings").Get<Settings>();
services.AddSingleton<AccessTokenRepository>();
services.AddSingleton(settings);
services.AddSingleton<AccessTokenRepository>();
services.AddSingleton(settings);
services.AddHttpClient<AzureSubscriptionRepository>();
services.AddHttpClient<AzureDeploymentRepository>();
services.AddHttpClient<AzureResourceRepository>();
services.AddHttpClient<AzureTagRepository>();
services.AddHttpClient<AzureResourceGroupRepository>();
services.AddHttpClient<AzureSubscriptionRepository>();
services.AddHttpClient<AzureDeploymentRepository>();
services.AddHttpClient<AzureResourceRepository>();
services.AddHttpClient<AzureTagRepository>();
services.AddHttpClient<AzureResourceGroupRepository>();
services.AddScoped<CreatedOnDateService>();
services.AddScoped<DeploymentTypeService>();
services.AddScoped<CreatedOnDateService>();
services.AddScoped<DeploymentTypeService>();
services.AddHostedService<OptionService>();
} );
}
services.AddHostedService<OptionService>();
});
}

View File

@@ -1,40 +1,40 @@
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using AzureRestApi.Models;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.Identity.Client;
namespace AzureRestApi.Repositories
namespace AzureRestApi.Repositories;
public class AccessTokenRepository(Settings settings)
{
public class AccessTokenRepository
private string? _accessToken;
public async Task<string> GetAccessToken()
{
private readonly Settings _settings;
private string? _accessToken;
if (!string.IsNullOrWhiteSpace(_accessToken)) return _accessToken;
public AccessTokenRepository(Settings settings)
var keyVaultUri = "https://" + settings.KeyVaultName + ".vault.azure.net";
var credential = new DefaultAzureCredential();
var client = new SecretClient(new Uri(keyVaultUri), credential);
var clientId = (await client.GetSecretAsync("ClientID")).Value.Value;
var clientSecret = (await client.GetSecretAsync("ClientSecret")).Value.Value;
var app = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithClientSecret(clientSecret)
.WithAuthority(new Uri($"https://login.microsoftonline.com/{settings.AzureTenantId}"))
.Build();
var scopes = new[] {"https://management.azure.com/.default"};
var result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
if (result == null)
{
_settings = settings;
throw new InvalidOperationException("Failed to obtain the Access token");
}
public async Task<string> GetAccessToken()
{
if (!string.IsNullOrWhiteSpace(_accessToken)) return _accessToken;
_accessToken = result.AccessToken;
var keyvaultUri = "https://" + _settings.KeyVaultName + ".vault.azure.net";
var credential = new DefaultAzureCredential();
var client = new SecretClient(new Uri(keyvaultUri), credential);
var clientId = (await client.GetSecretAsync("ClientID")).Value.Value;
var clientSecret = (await client.GetSecretAsync("ClientSecret")).Value.Value;
ClientCredential cc = new(clientId, clientSecret);
var context = new AuthenticationContext("https://login.microsoftonline.com/" + _settings.AzureTenantId);
var result = context.AcquireTokenAsync("https://management.azure.com/", cc);
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the Access token");
}
_accessToken = result.Result.AccessToken;
return _accessToken;
}
return _accessToken;
}
}
}

View File

@@ -2,43 +2,33 @@
using System.Net.Http.Headers;
using System.Text.Json;
namespace AzureRestApi.Repositories
namespace AzureRestApi.Repositories;
public abstract class AzureBaseRepository<T>(HttpClient httpClient, AccessTokenRepository accessTokenRepository)
{
public abstract class AzureBaseRepository<T>
protected async Task<List<T>> GetAllByUri(Uri uri)
{
private readonly HttpClient _httpClient;
private readonly AccessTokenRepository _accessTokenRepository;
var accessToken = await accessTokenRepository.GetAccessToken();
public AzureBaseRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository)
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
{
_httpClient = httpClient;
_accessTokenRepository = accessTokenRepository;
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var result = JsonSerializer.Deserialize<ApiResponse<T>>(responseString)?.value;
return result ?? [];
}
protected async Task<List<T>> GetAllByUri(Uri uri)
catch
{
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>();
}
return [];
}
}
}
}

View File

@@ -1,25 +1,20 @@
using AzureRestApi.Models.Resources;
namespace AzureRestApi.Repositories
{
public class AzureDeploymentRepository : AzureBaseRepository<Deployment>
{
public AzureDeploymentRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository) : base(httpClient, accessTokenRepository)
{
}
namespace AzureRestApi.Repositories;
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);
}
public class AzureDeploymentRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository)
: AzureBaseRepository<Deployment>(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

@@ -1,18 +1,14 @@
using AzureRestApi.Models.Resources;
namespace AzureRestApi.Repositories
{
public class AzureResourceGroupRepository : AzureBaseRepository<ResourceGroup>
{
public AzureResourceGroupRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository) : base(httpClient, accessTokenRepository)
{
}
namespace AzureRestApi.Repositories;
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);
}
public class AzureResourceGroupRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository)
: AzureBaseRepository<ResourceGroup>(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

@@ -1,21 +1,14 @@
using AzureRestApi.Models.Api;
using AzureRestApi.Models.Resources;
using System.Net.Http.Headers;
using System.Text.Json;
using AzureRestApi.Models.Resources;
namespace AzureRestApi.Repositories
namespace AzureRestApi.Repositories;
public class AzureResourceRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository)
: AzureBaseRepository<Resource>(httpClient, accessTokenRepository)
{
public class AzureResourceRepository : AzureBaseRepository<Resource>
public async Task<List<Resource>> GetAll(string subscriptionId)
{
public AzureResourceRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository) : base(httpClient, accessTokenRepository)
{
}
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 uri = new Uri($"https://management.azure.com/subscriptions/{subscriptionId}/resources?$expand=createdTime,changedTime&api-version=2021-04-01");
return await GetAllByUri(uri);
}
// GET https://management.azure.com/subscriptions/{subscriptionId}/resources?$filter={$filter}&$expand={$expand}&$top={$top}&api-version=2021-04-01
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

@@ -3,37 +3,26 @@ using AzureRestApi.Models.Resources;
using System.Net.Http.Headers;
using System.Text.Json;
namespace AzureRestApi.Repositories
namespace AzureRestApi.Repositories;
public class AzureSubscriptionRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository)
{
public class AzureSubscriptionRepository
public async Task<List<Subscription>?> GetAll()
{
private readonly HttpClient _httpClient;
private readonly AccessTokenRepository _accessTokenRepository;
// GET https://management.azure.com/subscriptions?api-version=2020-01-01
var accessToken = await accessTokenRepository.GetAccessToken();
var uri = new Uri("https://management.azure.com/subscriptions?api-version=2020-01-01");
public AzureSubscriptionRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository)
{
_httpClient = httpClient;
_accessTokenRepository = accessTokenRepository;
}
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);
public async Task<List<Subscription>?> GetAll()
{
// GET https://management.azure.com/subscriptions?api-version=2020-01-01
var response = await httpClient.SendAsync(request).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
var accessToken = await _accessTokenRepository.GetAccessToken();
var uri = new Uri("https://management.azure.com/subscriptions?api-version=2020-01-01");
var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
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< ApiResponse<Subscription>>(responseString)?.value;
}
return JsonSerializer.Deserialize< ApiResponse<Subscription>>(responseString)?.value;
}
}
}

View File

@@ -4,50 +4,38 @@ using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
namespace AzureRestApi.Repositories
namespace AzureRestApi.Repositories;
public class AzureTagRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository)
{
public class AzureTagRepository
public async Task<bool> SetTag(string scope, string name, string value)
{
private readonly HttpClient _httpClient;
private readonly AccessTokenRepository _accessTokenRepository;
private readonly ILogger<AzureTagRepository> _logger;
// PUT https://management.azure.com/{scope}/providers/Microsoft.Resources/tags/default?api-version=2021-04-01
var result = true;
var accessToken = await accessTokenRepository.GetAccessToken();
var uri = new Uri("https://management.azure.com/" + scope + "/providers/Microsoft.Resources/tags/default?api-version=2021-04-01");
public AzureTagRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository, ILogger<AzureTagRepository> logger)
var tagProperties = new TagProperties();
tagProperties.properties.tags.Add(name, value);
var body = JsonSerializer.Serialize(tagProperties);
HttpRequestMessage request = new(HttpMethod.Put, uri);
request.Headers.Accept.Clear();
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await httpClient.SendAsync(request).ConfigureAwait(false);
try
{
_httpClient = httpClient;
_accessTokenRepository = accessTokenRepository;
_logger = logger;
response.EnsureSuccessStatusCode();
await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}
catch
{
result = false;
}
public async Task<bool> SetTag(string scope, string name, string value)
{
// PUT https://management.azure.com/{scope}/providers/Microsoft.Resources/tags/default?api-version=2021-04-01
var result = true;
var accessToken = await _accessTokenRepository.GetAccessToken();
var uri = new Uri("https://management.azure.com/" + scope + "/providers/Microsoft.Resources/tags/default?api-version=2021-04-01");
var tagProperties = new TagProperties();
tagProperties.properties.tags.Add(name, value);
var body = JsonSerializer.Serialize(tagProperties);
HttpRequestMessage request = new(HttpMethod.Put, uri);
request.Headers.Accept.Clear();
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await _httpClient.SendAsync(request).ConfigureAwait(false);
try
{
response.EnsureSuccessStatusCode();
await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}
catch
{
result = false;
}
return result;
}
return result;
}
}
}

View File

@@ -1,65 +1,61 @@
using AzureRestApi.Repositories;
using Spectre.Console;
namespace AzureRestApi.Services
namespace AzureRestApi.Services;
public class CreatedOnDateService(
AzureSubscriptionRepository azureSubscriptionRepository,
AzureResourceRepository azureResourceRepository,
AzureTagRepository azureTagRepository)
{
public class CreatedOnDateService
private const string TagName = "CreatedOnDate";
public async Task SetCreatedOnDateTags(bool skipDone = true)
{
private const string TagName = "CreatedOnDate";
private readonly AzureSubscriptionRepository _azureSubscriptionRepository;
private readonly AzureResourceRepository _azureResourceRepository;
private readonly AzureTagRepository _azureTagRepository;
public CreatedOnDateService(AzureSubscriptionRepository azureSubscriptionRepository, AzureResourceRepository azureResourceRepository, AzureTagRepository azureTagRepository)
var rule = new Rule("[skyblue1]Setting CreatedOnDateTags[/]")
{
_azureSubscriptionRepository = azureSubscriptionRepository;
_azureResourceRepository = azureResourceRepository;
_azureTagRepository = azureTagRepository;
}
Justification = Justify.Left,
Style = Style.Parse("skyblue1")
};
AnsiConsole.Write(rule);
public async Task SetCreatedOnDateTags(bool skipDone = true)
{
var rule = new Rule("[skyblue1]Setting CreatedOnDateTags[/]");
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 =>
{
var subscriptions = await azureSubscriptionRepository.GetAll();
if (subscriptions == null || subscriptions.Count == 0) throw new Exception("No subscriptions found");
subscriptions = subscriptions.Where(s => s.Enabled && !string.IsNullOrWhiteSpace(s.subscriptionId)).ToList();
if (subscriptions.Count == 0) throw new Exception("No subscriptions found");
await AnsiConsole.Status()
.AutoRefresh(true)
.Spinner(Spinner.Known.Default)
.StartAsync("Retrieving subscriptions...", async ctx =>
foreach (var subscription in 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 = new Rule($"[skyblue1]{subscription.displayName}[/]");
rule.Alignment = Justify.Left;
rule.Style = Style.Parse("skyblue1 dim");
AnsiConsole.Write(rule);
Justification = Justify.Left,
Style = Style.Parse("skyblue1 dim")
};
AnsiConsole.Write(rule);
ctx.Status($"Getting the resources for subscription '{subscription.displayName}'");
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();
var resources = await azureResourceRepository.GetAll(subscription.subscriptionId);
if (resources == null || resources.Count == 0) continue;
resources = resources.Where(r => !string.IsNullOrWhiteSpace(r.CreatedOn) && !string.IsNullOrWhiteSpace(r.id)).ToList();
if (skipDone)
{
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)
{
var tagSuccess = await _azureTagRepository.SetTag(resource.id, TagName, resource.CreatedOn);
AnsiConsole.MarkupLine($"{resource.id} " + (tagSuccess ? "[green]V[/]" : "[red]X[/]"));
}
if (skipDone)
{
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)
{
var tagSuccess = await azureTagRepository.SetTag(resource.id, TagName, resource.CreatedOn);
AnsiConsole.MarkupLine($"{resource.id} " + (tagSuccess ? "[green]V[/]" : "[red]X[/]"));
}
}
});
}
}
}

View File

@@ -1,73 +1,65 @@
using AzureRestApi.Repositories;
using Spectre.Console;
namespace AzureRestApi.Services
namespace AzureRestApi.Services;
public class DeploymentTypeService(
AzureSubscriptionRepository azureSubscriptionRepository,
AzureDeploymentRepository azureDeploymentRepository,
AzureResourceGroupRepository azureResourceGroupRepository)
{
public class DeploymentTypeService
public async Task CheckDeploymentTypes()
{
private readonly AzureSubscriptionRepository _azureSubscriptionRepository;
private readonly AzureDeploymentRepository _azureDeploymentRepository;
private readonly AzureResourceGroupRepository _azureResourceGroupRepository;
public DeploymentTypeService(AzureSubscriptionRepository azureSubscriptionRepository, AzureDeploymentRepository azureDeploymentRepository, AzureResourceGroupRepository azureResourceGroupRepository)
var rule = new Rule("[skyblue1]Checking deployment types[/]")
{
_azureSubscriptionRepository = azureSubscriptionRepository;
_azureDeploymentRepository = azureDeploymentRepository;
_azureResourceGroupRepository = azureResourceGroupRepository;
}
Justification = Justify.Left,
Style = Style.Parse("skyblue1")
};
AnsiConsole.Write(rule);
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.Count == 0) throw new Exception("No subscriptions found");
await AnsiConsole.Status()
.AutoRefresh(true)
.Spinner(Spinner.Known.Default)
.StartAsync("Retrieving subscriptions...", async ctx =>
foreach (var subscription in subscriptions)
{
// 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 = 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}"));
}
}
Justification = Justify.Left,
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.Count == 0) 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,64 +1,47 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Spectre.Console;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AzureRestApi.Services
namespace AzureRestApi.Services;
public class OptionService(CreatedOnDateService createdOnDateService, DeploymentTypeService deploymentTypeService)
: IHostedService
{
public class OptionService : IHostedService
public async Task StartAsync(CancellationToken cancellationToken)
{
private readonly ILogger<OptionService> _logger;
private readonly CreatedOnDateService _createdOnDateService;
private readonly DeploymentTypeService _deploymentTypeService;
var rule = new Rule("[yellow]Cloud Egineering Console App[/]");
AnsiConsole.Write(rule);
public OptionService(ILogger<OptionService> logger, CreatedOnDateService createdOnDateService, DeploymentTypeService deploymentTypeService)
Console.WriteLine("-- This couldn't be done with Powershell, so here we are.... ");
Console.WriteLine();
var choices = new[]
{
_logger = logger;
_createdOnDateService = createdOnDateService;
_deploymentTypeService = deploymentTypeService;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
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();
var choices = new[]
{
"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 (result == choices[0])
{
await _createdOnDateService.SetCreatedOnDateTags(true);
}
else if (result == choices[1])
{
await _deploymentTypeService.CheckDeploymentTypes();
}
rule = new Rule("[yellow]Done. Bye.[/]");
AnsiConsole.Write(rule);
}
public Task StopAsync(CancellationToken cancellationToken)
{
throw new NotImplementedException();
"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 (result == choices[0])
{
await createdOnDateService.SetCreatedOnDateTags(true);
}
else if (result == choices[1])
{
await deploymentTypeService.CheckDeploymentTypes();
}
rule = new Rule("[yellow]Done. Bye.[/]");
AnsiConsole.Write(rule);
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}

View File

@@ -1,8 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
</Project>

View File

@@ -1,12 +0,0 @@
using System;
namespace ConsoleApp1
{
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}