mirror of
https://dev.azure.com/effectory/Survey%20Software/_git/Cloud%20Engineering
synced 2026-02-27 18:52:18 +01:00
Added Creatondate consoleapp and tagging policy
This commit is contained in:
25
ConsoleApps/AzureRestApi/AzureRestApi.sln
Normal file
25
ConsoleApps/AzureRestApi/AzureRestApi.sln
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.1.32328.378
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureRestApi", "AzureRestApi\AzureRestApi.csproj", "{DCEBAB23-4E5B-4FBE-9752-EC9C22E5C394}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{DCEBAB23-4E5B-4FBE-9752-EC9C22E5C394}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DCEBAB23-4E5B-4FBE-9752-EC9C22E5C394}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DCEBAB23-4E5B-4FBE-9752-EC9C22E5C394}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DCEBAB23-4E5B-4FBE-9752-EC9C22E5C394}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {9F84ED05-3BCF-4BA7-B4ED-FEF688F8A2D0}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
26
ConsoleApps/AzureRestApi/AzureRestApi/AzureRestApi.csproj
Normal file
26
ConsoleApps/AzureRestApi/AzureRestApi/AzureRestApi.csproj
Normal file
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.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" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="appsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,9 @@
|
||||
using AzureRestApi.Models.Resources;
|
||||
|
||||
namespace AzureRestApi.Models.Api
|
||||
{
|
||||
public class ResourcesResponse
|
||||
{
|
||||
public List<Resource>? value { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using AzureRestApi.Models.Resources;
|
||||
|
||||
namespace AzureRestApi.Models.Api
|
||||
{
|
||||
public class SubscriptionResponse
|
||||
{
|
||||
public List<Subscription>? value { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using AzureRestApi.Models.Resources;
|
||||
|
||||
namespace AzureRestApi.Models.Api
|
||||
{
|
||||
public class TagProperties
|
||||
{
|
||||
public TagTags properties { get; set; } = new TagTags();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
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>();
|
||||
|
||||
public string? CreatedOn
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(createdTime))
|
||||
{
|
||||
return changedTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
return createdTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
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; }
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return state == "Enabled";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace AzureRestApi.Models.Resources
|
||||
{
|
||||
public class TagTags
|
||||
{
|
||||
public Dictionary<string, string> tags { get; set; } = new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
8
ConsoleApps/AzureRestApi/AzureRestApi/Models/Settings.cs
Normal file
8
ConsoleApps/AzureRestApi/AzureRestApi/Models/Settings.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace AzureRestApi.Models
|
||||
{
|
||||
public class Settings
|
||||
{
|
||||
public string KeyVaultName { get; set; }
|
||||
public string AzureTenantId { get; set; }
|
||||
}
|
||||
}
|
||||
38
ConsoleApps/AzureRestApi/AzureRestApi/Program.cs
Normal file
38
ConsoleApps/AzureRestApi/AzureRestApi/Program.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using AzureRestApi.Models;
|
||||
using AzureRestApi.Repositories;
|
||||
using AzureRestApi.Services;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace AzureRestApi
|
||||
{
|
||||
class Program
|
||||
{
|
||||
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();
|
||||
|
||||
Settings settings = config.GetRequiredSection("Settings").Get<Settings>();
|
||||
|
||||
services.AddSingleton<AccessTokenRepository>();
|
||||
services.AddSingleton(settings);
|
||||
|
||||
services.AddHttpClient<AzureSubscriptionRepository>();
|
||||
services.AddHttpClient<AzureResourceRepository>();
|
||||
services.AddHttpClient<AzureTagRepository>();
|
||||
|
||||
services.AddScoped<CreatedOnDateService>();
|
||||
|
||||
services.AddHostedService<OptionService>();
|
||||
} );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Azure.Identity;
|
||||
using Azure.Security.KeyVault.Secrets;
|
||||
using AzureRestApi.Models;
|
||||
using Microsoft.IdentityModel.Clients.ActiveDirectory;
|
||||
|
||||
namespace AzureRestApi.Repositories
|
||||
{
|
||||
public class AccessTokenRepository
|
||||
{
|
||||
private readonly Settings _settings;
|
||||
private string? _accessToken;
|
||||
|
||||
public AccessTokenRepository(Settings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public async Task<string> GetAccessToken()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_accessToken)) return _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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using AzureRestApi.Models.Api;
|
||||
using AzureRestApi.Models.Resources;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace AzureRestApi.Repositories
|
||||
{
|
||||
public class AzureResourceRepository
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly AccessTokenRepository _accessTokenRepository;
|
||||
|
||||
public AzureResourceRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_accessTokenRepository = accessTokenRepository;
|
||||
}
|
||||
|
||||
public async Task<List<Resource>?> GetAllResources(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using AzureRestApi.Models.Api;
|
||||
using AzureRestApi.Models.Resources;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace AzureRestApi.Repositories
|
||||
{
|
||||
public class AzureSubscriptionRepository
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly AccessTokenRepository _accessTokenRepository;
|
||||
|
||||
public AzureSubscriptionRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_accessTokenRepository = accessTokenRepository;
|
||||
}
|
||||
|
||||
public async Task<List<Subscription>?> GetAllSubscriptions()
|
||||
{
|
||||
// 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");
|
||||
|
||||
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<SubscriptionResponse>(responseString)?.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using AzureRestApi.Models.Api;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace AzureRestApi.Repositories
|
||||
{
|
||||
public class AzureTagRepository
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly AccessTokenRepository _accessTokenRepository;
|
||||
private readonly ILogger<AzureTagRepository> _logger;
|
||||
|
||||
public AzureTagRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository, ILogger<AzureTagRepository> logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_accessTokenRepository = accessTokenRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using AzureRestApi.Repositories;
|
||||
|
||||
namespace AzureRestApi.Services
|
||||
{
|
||||
public class CreatedOnDateService
|
||||
{
|
||||
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)
|
||||
{
|
||||
_azureSubscriptionRepository = azureSubscriptionRepository;
|
||||
_azureResourceRepository = azureResourceRepository;
|
||||
_azureTagRepository = azureTagRepository;
|
||||
}
|
||||
|
||||
public async Task SetCreatedOnDateTags(bool skipDone = true)
|
||||
{
|
||||
var defaultForeGround = Console.ForegroundColor;
|
||||
Console.WriteLine("==============================================");
|
||||
Console.WriteLine("Setting CreatedOnDateTags");
|
||||
Console.WriteLine("==============================================");
|
||||
|
||||
var subscriptions = await _azureSubscriptionRepository.GetAllSubscriptions();
|
||||
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("----------------------------------------------");
|
||||
|
||||
var resources = await _azureResourceRepository.GetAllResources(subscription.subscriptionId);
|
||||
if (resources == null || !resources.Any()) 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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
Console.WriteLine("==============================================");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AzureRestApi.Services
|
||||
{
|
||||
public class OptionService : IHostedService
|
||||
{
|
||||
private readonly ILogger<OptionService> _logger;
|
||||
private readonly CreatedOnDateService _createdOnDateService;
|
||||
|
||||
public OptionService(ILogger<OptionService> logger, CreatedOnDateService createdOnDateService)
|
||||
{
|
||||
_logger = logger;
|
||||
_createdOnDateService = createdOnDateService;
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
int userInput = 0;
|
||||
bool validatedInput = false;
|
||||
while (!validatedInput)
|
||||
{
|
||||
Console.WriteLine("================================================================");
|
||||
Console.WriteLine("Cloud Egineering Console App");
|
||||
Console.WriteLine("================================================================");
|
||||
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)
|
||||
{
|
||||
Console.WriteLine("Please enter a valid choice");
|
||||
}
|
||||
}
|
||||
|
||||
if (userInput == 1)
|
||||
{
|
||||
await _createdOnDateService.SetCreatedOnDateTags(true);
|
||||
}
|
||||
|
||||
Console.WriteLine("Bye.");
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
13
ConsoleApps/AzureRestApi/AzureRestApi/appsettings.json
Normal file
13
ConsoleApps/AzureRestApi/AzureRestApi/appsettings.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"Settings": {
|
||||
"KeyVaultName": "consoleapp",
|
||||
"AzureTenantId": "e9792fd7-4044-47e7-a40d-3fba46f1cd09"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "None"
|
||||
}
|
||||
}
|
||||
}
|
||||
8
ConsoleApps/AzureRestApi/ConsoleApp1/ConsoleApp1.csproj
Normal file
8
ConsoleApps/AzureRestApi/ConsoleApp1/ConsoleApp1.csproj
Normal file
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
12
ConsoleApps/AzureRestApi/ConsoleApp1/Program.cs
Normal file
12
ConsoleApps/AzureRestApi/ConsoleApp1/Program.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace ConsoleApp1
|
||||
{
|
||||
internal class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("Hello World!");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user