Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixed issue "Remove-FinOpsHub fails when there's only one resource" #983

Merged
merged 6 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"ms-azuretools.vscode-bicep",
"yzhang.markdown-all-in-one",
"bierner.markdown-mermaid",
"powerquery.vscode-powerquery"
"powerquery.vscode-powerquery",
"ms-vscode.powershell"
]
}
25 changes: 15 additions & 10 deletions src/powershell/Private/Get-HubIdentifier.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,28 @@

Returns the string '123456hyfpqje'.
#>
function Get-HubIdentifier
function Get-HubIdentifier
flanakin marked this conversation as resolved.
Show resolved Hide resolved
{
param
(
param (
flanakin marked this conversation as resolved.
Show resolved Hide resolved
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string[]]
$Collection
)

$idPattern = '[a-z0-9]{13}' # Matches exactly 13 alphanumeric characters
$substrings = @()

foreach ($string in $Collection)
{
# Use regex to find valid 13-character matches
$match = [regex]::Matches($string, $idPattern)
if ($match.Count -gt 0)
{
$substrings += $match | ForEach-Object { $_.Value }
}

# Generate all possible substrings
for ($startIndex = 0; $startIndex -lt $string.Length; $startIndex++)
{
for ($endIndex = 1; $endIndex -le ($string.Length - $startIndex); $endIndex++)
Expand All @@ -34,11 +43,7 @@ function Get-HubIdentifier
}
}

$id = $substrings | Group-Object | Where-Object -FilterScript {$_.count -eq $Collection.length -and $_.Name.Length -eq 13} | Select-Object -Expand 'Name'
if ($id -notcontains '-' -and $id -notcontains '_')
{
return $id
}

return $null
# Filter out matches that have hyphens or underscores and return the first valid match
$validMatches = $substrings | Group-Object | Where-Object { $_.Count -eq $Collection.Length -and $_.Name.Length -eq 13 -and $_.Name -notmatch '[-_]' } | Select-Object -ExpandProperty Name
return $validMatches | Select-Object -First 1
}
88 changes: 66 additions & 22 deletions src/powershell/Public/Remove-FinOpsHub.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
.DESCRIPTION
The Remove-FinOpsHub command deletes a FinOps Hub instance and optionally deletes the storage account hosting cost data.

The comamnd returns a boolean value indicating whether all resources were successfully deleted.
The command returns a boolean value indicating whether all resources were successfully deleted.

.PARAMETER Name
Required when specifying Name. Name of the FinOps Hub.
Expand All @@ -22,40 +22,37 @@
.PARAMETER KeepStorageAccount
Optional. Indicates that the storage account associated with the FinOps Hub should be retained.

.PARAMETER Force
Optional. Deletes specified resources without asking for a confirmation.

.EXAMPLE
Remove-FinOpsHub -Name MyHub -ResourceGroupName MyRG -KeepStorageAccount

Deletes a FinOps Hub named MyHub and deletes all associated resource except the storagea ccount.
Deletes a FinOps Hub named MyHub and deletes all associated resources except the storage account.
#>

function Remove-FinOpsHub
{
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
param
(
param (
flanakin marked this conversation as resolved.
Show resolved Hide resolved
[Parameter(Mandatory = $true, ParameterSetName = 'Name')]
[ValidateNotNullOrEmpty ()]
[string]
$Name,
[ValidateNotNullOrEmpty()]
[string]$Name,
flanakin marked this conversation as resolved.
Show resolved Hide resolved

[Parameter(ParameterSetName = 'Name')]
[string]
$ResourceGroupName,
[string]$ResourceGroupName,
flanakin marked this conversation as resolved.
Show resolved Hide resolved

[Parameter(Mandatory = $true, ParameterSetName = 'Object')]
[ValidateNotNullOrEmpty ()]
[psobject]
$InputObject,
[ValidateNotNullOrEmpty()]
[psobject]$InputObject,
flanakin marked this conversation as resolved.
Show resolved Hide resolved

[Parameter(ParameterSetName = 'Name')]
[Parameter(ParameterSetName = 'Object')]
[switch]
$KeepStorageAccount,
[switch]$KeepStorageAccount,
flanakin marked this conversation as resolved.
Show resolved Hide resolved

[Parameter(ParameterSetName = 'Name')]
[Parameter(ParameterSetName = 'Object')]
[switch]
$Force
[switch]$Force
flanakin marked this conversation as resolved.
Show resolved Hide resolved
)

$context = Get-AzContext
Expand All @@ -66,7 +63,6 @@ function Remove-FinOpsHub

try
{

if ($PSCmdlet.ParameterSetName -eq 'Name')
{
if (-not [string]::IsNullOrEmpty($ResourceGroupName))
Expand All @@ -91,18 +87,66 @@ function Remove-FinOpsHub
throw $script:LocalizedData.Hub_Remove_NotFound -f $Name
}

Write-Verbose -Message "Found FinOps Hub: $Name in resource group $ResourceGroupName"

$uniqueId = Get-HubIdentifier -Collection $hub.Resources.Name
Write-Verbose -Message "Unique identifier: $uniqueId"

$resources = Get-AzResource -ResourceGroupName $ResourceGroupName |
Where-Object -FilterScript { $_.Name -like "*$uniqueId*" -and ((-not $KeepStorageAccount) -or $_.ResourceType -ne "Microsoft.Storage/storageAccounts") }
Write-Verbose -Message "Filtered Resources: $($resources | ForEach-Object { $_.Name })"

if ($null -eq $resources)
{
Write-Warning "No resources found to delete."
return $false
}

$resources = Get-AzResource -ResourceGroupName $ResourceGroupName | Where-Object -FilterScript { $_.Name -like "*$uniqueId*" -and ((-not $KeepStorageAccount) -or $_.ResourceType -ne "Microsoft.Storage/storageAccounts") }
Write-Verbose -Message "Resources to be deleted: $($resources | ForEach-Object { $_.Name })"

# Temporarily set $ConfirmPreference to None if -Force is specified
if ($Force)
{
$originalConfirmPreference = $ConfirmPreference
$ConfirmPreference = 'None'
}

if ($PSCmdlet.ShouldProcess($Name, 'DeleteFinOpsHub'))
{
return ($resources | Remove-AzResource -Force:$Force).Reduce({ $args[0] -and $args[1] }, $true)
$success = $true
foreach ($resource in $resources)
{
try
{
Write-Verbose -Message "Deleting resource: $($resource.Name)"
Remove-AzResource -ResourceId $resource.ResourceId -Force:$Force -ErrorAction Stop
}
catch
{
Write-Error -Message "Failed to delete resource: $($resource.Name). Error: $_"
$success = $false
}
}

# Restore the original $ConfirmPreference
if ($Force)
{
$ConfirmPreference = $originalConfirmPreference
}

return $success
}
}
catch
{
throw ($script:LocalizedData.Hub_Remove_Failed -f $_)
Write-Error -Message "Failed to remove FinOps hub. Error: $_"
if ($_.Exception.InnerException)
{
throw "Detailed Error: $($_.Exception.InnerException.Message)"
}
else
{
throw "Detailed Error: $($_.Exception.Message)"
}
}
}

}
70 changes: 51 additions & 19 deletions src/powershell/Tests/Integration/Hubs.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@

Describe 'Hubs' {
BeforeDiscovery {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
$requiredRPs = $global:ftk_InitializeTests_Hubs_RequiredRPs

[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")]
$versions = @('0.0.1', '0.1', '0.1.1') | Sort-Object { [version]$_ }
# TODO: Automatically validate the last 3 versions only
# TODO: Automatically validate the last 3 versions only
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
$versions = @('0.1.1', '0.3', '0.4', '0.5') | Sort-Object { [version]$_ }
}

BeforeAll {
# Must be duplicated because pre-discovery vars aren't accessible
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
$requiredRPs = $global:ftk_InitializeTests_Hubs_RequiredRPs
}

Expand All @@ -37,16 +39,16 @@ Describe 'Hubs' {
Context 'Deploy-FinOpsHub' {
BeforeAll {
# Arrange
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
$ftk_ResourceGroup = "ftk-test-integration"
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")]
$name = "ftk-test-DeployHub_$($env:USERNAME)"
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")]
$rg = "ftk-test-integration"
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
$ftk_HubName = "ftk-test-DeployHub_$($env:USERNAME)"
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
$ftk_HubRG = "ftk-test-integration"
# TODO: Confirm lowercase/space requirement and add handling to avoid the limitation
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")]
$location = "eastus" # must be lowercase with no spaces
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
$ftk_HubLocation = "eastus" # must be lowercase with no spaces
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
$versions = Get-FinOpsToolkitVersion | Select-Object -ExpandProperty Version -Unique | Sort-Object { $_ }
}

Expand Down Expand Up @@ -81,26 +83,56 @@ Describe 'Hubs' {
}
}

Context 'Deploy and remove' {
It 'Should deploy and remove a FinOps hubs instance' {
Monitor "Deploying latest" -Indent ' ' {
Monitor "Deploying..." {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Scope = 'Function', Target = '*')]
$script:deployResult = Deploy-FinOpsHub `
-Name $ftk_HubName `
-Location $ftk_HubLocation `
-ResourceGroupName $ftk_HubRG
Report -Object ($script:deployResult ?? '(null)')
}

Monitor 'Getting instance...' {
$script:getResult = Get-FinOpsHub -Name $ftk_HubName -ResourceGroupName $ftk_HubRG
Report -Object ($script:getResult ?? '(null)')
}

Monitor 'Removing instance...' {
$script:removeaResult = Remove-FinOpsHub -Name $ftk_HubName -ResourceGroupName $ftk_HubRG -Force
Report -Object ($script:removeResult ?? '(null)')
}
}
}
}

Context 'Deploy and upgrade' -Skip {
It 'Should deploy FinOps hubs <_>' -ForEach $versions {
$ver = $_

# Act
Monitor "FinOps hubs $ver" -Indent ' ' {
Monitor "Deploying..." {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute ('PSUseDeclaredVarsMoreThanAssignments', Scope = 'Function', Target = '*')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute ('PSUseDeclaredVarsMoreThanAssignments', '', Scope = 'Function', Target = '*')]
$script:deployResult = Deploy-FinOpsHub `
-Name $name `
-Location $location `
-ResourceGroupName $rg `
-Name $ftk_HubName `
-Location $ftk_HubLocation `
-ResourceGroupName $ftk_HubRG `
-Version $_
Report -Object ($script:deployResult ?? '(null)')
}

Monitor 'Getting instance...' {
$script:getResult = Get-FinOpsHub -Name $name -ResourceGroupName $rg
$script:getResult = Get-FinOpsHub -Name $ftk_HubName -ResourceGroupName $ftk_HubRG
Report -Object ($script:getResult ?? '(null)')
}

Monitor 'Removing instance...' {
$script:removeResult = Remove-FinOpsHub -Name $ftk_HubName -ResourceGroupName $ftk_HubRG -Force
Report -Object ($script:removeResult ?? '(null)')
}
}

# Assert RP status
Expand All @@ -111,8 +143,8 @@ Describe 'Hubs' {
}

# Assert hub state
@($script:getResult).Count | Should -Be 1 -ErrorAction Continue -Because "there should only be one hub with name '$name' in resource group '$rg' (v$ver)"
$script:getResult.Location.ToLower() -replace ' ', '' | Should -Be $location -ErrorAction Continue -Because "hub should be in location '$location' (v$ver)"
@($script:getResult).Count | Should -Be 1 -ErrorAction Continue -Because "there should only be one hub with name '$ftk_HubName' in resource group '$ftk_HubRG' (v$ver)"
$script:getResult.Location.ToLower() -replace ' ', '' | Should -Be $ftk_HubLocation -ErrorAction Continue -Because "hub should be in location '$ftk_HubLocation' (v$ver)"
$script:getResult.Version | Should -Be $ver -ErrorAction Continue
$script:getResult.Status | Should -Be 'Deployed' -ErrorAction Continue -Because "hub should be in 'Deployed' status (v$ver)"
$script:getResult.Status | Should -Not -Be 'Unknown' -ErrorAction Continue -Because "hub should not be in 'Unknown' status (v$ver)"
Expand Down
Loading