diff --git a/Tingle.AzureCleaner.Tests/AzureCleanerTests.cs b/Tingle.AzureCleaner.Tests/AzureCleanerTests.cs index 1c67341..6cc3a3b 100644 --- a/Tingle.AzureCleaner.Tests/AzureCleanerTests.cs +++ b/Tingle.AzureCleaner.Tests/AzureCleanerTests.cs @@ -117,7 +117,7 @@ class ModifiedAzureCleaner(IMemoryCache cache, IOptions opt public List> DeleteAzureResourcesAsyncCalls { get; } = []; public List<(AzdoProjectUrl url, string? token, IReadOnlyCollection possibleNames)> DeleteReviewAppsEnvironmentsAsyncCalls { get; } = []; - protected override Task DeleteAzureResourcesAsync(IReadOnlyCollection possibleNames, CancellationToken cancellationToken = default) + protected override Task DeleteAzureResourcesAsync(IReadOnlyCollection possibleNames, IReadOnlyCollection subscriptionIdsOrNames, CancellationToken cancellationToken = default) { DeleteAzureResourcesAsyncCalls.Add(possibleNames); return Task.CompletedTask; diff --git a/Tingle.AzureCleaner.Tests/EndpointTests.cs b/Tingle.AzureCleaner.Tests/EndpointTests.cs index 04d4d16..6171e63 100644 --- a/Tingle.AzureCleaner.Tests/EndpointTests.cs +++ b/Tingle.AzureCleaner.Tests/EndpointTests.cs @@ -178,7 +178,7 @@ class ModifiedAzureCleaner(IMemoryCache cache, IOptions opt public List> DeleteAzureResourcesAsyncCalls { get; } = []; public List<(AzdoProjectUrl url, string? token, IReadOnlyCollection possibleNames)> DeleteReviewAppsEnvironmentsAsyncCalls { get; } = []; - protected override Task DeleteAzureResourcesAsync(IReadOnlyCollection possibleNames, CancellationToken cancellationToken = default) + protected override Task DeleteAzureResourcesAsync(IReadOnlyCollection possibleNames, IReadOnlyCollection subscriptionIdsOrNames, CancellationToken cancellationToken = default) { DeleteAzureResourcesAsyncCalls.Add(possibleNames); return Task.CompletedTask; diff --git a/Tingle.AzureCleaner/AzdoCleanupEvent.cs b/Tingle.AzureCleaner/AzdoCleanupEvent.cs index b21a87c..51fda52 100644 --- a/Tingle.AzureCleaner/AzdoCleanupEvent.cs +++ b/Tingle.AzureCleaner/AzdoCleanupEvent.cs @@ -1,5 +1,4 @@ -using Tingle.AzureCleaner; -using Tingle.EventBus; +using Tingle.EventBus; namespace Tingle.AzureCleaner; diff --git a/Tingle.AzureCleaner/AzureCleaner.cs b/Tingle.AzureCleaner/AzureCleaner.cs index efbef61..15b97a0 100644 --- a/Tingle.AzureCleaner/AzureCleaner.cs +++ b/Tingle.AzureCleaner/AzureCleaner.cs @@ -43,7 +43,11 @@ public AzureCleaner(IMemoryCache cache, IOptions options, I azdoProjects = this.options.AzdoProjects.Select(e => e.Split(";")).ToDictionary(s => s[0], s => s[1]); } - public virtual async Task HandleAsync(int prId, string? remoteUrl = null, string? rawProjectUrl = null, CancellationToken cancellationToken = default) + public virtual async Task HandleAsync(int prId, + IReadOnlyCollection? subscriptionIdsOrNames = null, + string? remoteUrl = null, + string? rawProjectUrl = null, + CancellationToken cancellationToken = default) { var possibleNames = MakePossibleNames([prId]); @@ -60,7 +64,8 @@ public virtual async Task HandleAsync(int prId, string? remoteUrl = null, string } } - await DeleteAzureResourcesAsync(possibleNames, cancellationToken); + subscriptionIdsOrNames ??= options.Subscriptions; + await DeleteAzureResourcesAsync(possibleNames: possibleNames, subscriptionIdsOrNames: subscriptionIdsOrNames, cancellationToken: cancellationToken); } internal virtual bool TryFindAzdoProject(string? rawUrl, out AzdoProjectUrl url, [NotNullWhen(true)] out string? token) @@ -73,7 +78,7 @@ internal virtual bool TryFindAzdoProject(string? rawUrl, out AzdoProjectUrl url, return azdoProjects.TryGetValue(url, out token); } - protected virtual async Task DeleteAzureResourcesAsync(IReadOnlyCollection possibleNames, CancellationToken cancellationToken = default) + protected virtual async Task DeleteAzureResourcesAsync(IReadOnlyCollection possibleNames, IReadOnlyCollection subscriptionIdsOrNames, CancellationToken cancellationToken = default) { var credential = new DefaultAzureCredential(); var client = new ArmClient(credential); @@ -82,6 +87,15 @@ protected virtual async Task DeleteAzureResourcesAsync(IReadOnlyCollection 0 + && !subscriptionIdsOrNames.Contains(sub.Data.SubscriptionId, StringComparer.OrdinalIgnoreCase) + && !subscriptionIdsOrNames.Contains(sub.Data.DisplayName, StringComparer.OrdinalIgnoreCase)) + { + logger.LogDebug("Skipping subscription '{SubscriptionName} ({SubscriptionId})' ...", sub.Data.DisplayName, sub.Data.SubscriptionId); + continue; + } + logger.LogDebug("Searching in subscription '{SubscriptionName} ({SubscriptionId})' ...", sub.Data.DisplayName, sub.Data.SubscriptionId); // resource group is deleted first to avoid repetition on dependent resources, it makes it easier @@ -867,6 +881,12 @@ public class AzureCleanerOptions { public List AzdoProjects { get; set; } = []; + /// + /// Name or ID of subscriptions allowed. + /// If none are provided, all subscriptions are checked. + /// + public List Subscriptions { get; set; } = []; + public bool AzureResourceGroups { get; set; } = true; public bool AzureKubernetes { get; set; } = true; public bool AzureWebsites { get; set; } = true; diff --git a/Tingle.AzureCleaner/Program.cs b/Tingle.AzureCleaner/Program.cs index d08d788..b064e3a 100644 --- a/Tingle.AzureCleaner/Program.cs +++ b/Tingle.AzureCleaner/Program.cs @@ -36,14 +36,18 @@ { var root = new RootCommand("Cleanup tool for Azure resources based on Azure DevOps PRs") { - new Option(["-p", "--pr", "--pull-request-id"], "Identifier of the pull request.") { IsRequired = true, }, + new Option(["-p", "--pr", "--pull-request", "--pull-request-id"], "Identifier of the pull request.") { IsRequired = true, }, + new Option(["-s", "--subscription"], "Name or ID of subscriptions allowed. If none are provided, all subscriptions are checked."), new Option(["--remote", "--remote-url"], "Remote URL of the Azure DevOps repository."), new Option(["--project", "--project-url"], "Project URL. Overrides the remote URL when provided."), }; - root.Handler = CommandHandler.Create(async (IHost host, int pullRequestId, string? remoteUrl, string? projectUrl) => + root.Handler = CommandHandler.Create(async (IHost host, int pullRequestId, string[] subscription, string? remoteUrl, string? projectUrl) => { var cleaner = host.Services.GetRequiredService(); - await cleaner.HandleAsync(prId: pullRequestId, remoteUrl: remoteUrl, rawProjectUrl: projectUrl); + await cleaner.HandleAsync(prId: pullRequestId, + subscriptionIdsOrNames: subscription, + remoteUrl: remoteUrl, + rawProjectUrl: projectUrl); }); var clb = new CommandLineBuilder(root) diff --git a/Tingle.AzureCleaner/Properties/launchSettings.json b/Tingle.AzureCleaner/Properties/launchSettings.json index 511d9f7..1a92a1c 100644 --- a/Tingle.AzureCleaner/Properties/launchSettings.json +++ b/Tingle.AzureCleaner/Properties/launchSettings.json @@ -3,7 +3,7 @@ "profiles": { "Tingle.AzureCleaner": { "commandName": "Project", - //"commandLineArgs": "Tingle.AzureCleaner --pr 12", + //"commandLineArgs": "Tingle.AzureCleaner --pr 3 --subscription DEPENDABOT", "launchBrowser": true, "launchUrl": "health", "environmentVariables": {