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

Reduce unnecessary delete calls to ARM for storage accounts #9292

Merged
merged 1 commit into from
Oct 31, 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
190 changes: 93 additions & 97 deletions eng/common/scripts/Helpers/Resource-Helpers.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -232,104 +232,10 @@ function Remove-WormStorageAccounts() {
foreach ($group in $groups) {
Write-Host "========================================="
$accounts = Get-AzStorageAccount -ResourceGroupName $group.ResourceGroupName
if ($accounts) {
foreach ($account in $accounts) {
if ($WhatIfPreference) {
Write-Host "What if: Removing $($account.StorageAccountName) in $($account.ResourceGroupName)"
}
else {
Write-Host "Removing $($account.StorageAccountName) in $($account.ResourceGroupName)"
}

$hasContainers = ($account.Kind -ne "FileStorage")

# If it doesn't have containers then we can skip the explicit clean-up of this storage account
if (!$hasContainers) { continue }

$ctx = New-AzStorageContext -StorageAccountName $account.StorageAccountName
$containers = $ctx | Get-AzStorageContainer
$blobs = $containers | Get-AzStorageBlob

$immutableBlobs = $containers `
| Where-Object { $_.BlobContainerProperties.HasImmutableStorageWithVersioning } `
| Get-AzStorageBlob
try {
foreach ($blob in $immutableBlobs) {
# We can't edit blobs with customer encryption without using that key
# so just try to delete them fully instead. It is unlikely they
# will also have a legal hold enabled.
if (($blob | Get-Member 'ListBlobProperties') `
-and $blob.ListBlobProperties.Properties.CustomerProvidedKeySha256) {
Write-Host "Removing customer encrypted blob: $($blob.Name), account: $($account.StorageAccountName), group: $($group.ResourceGroupName)"
$blob | Remove-AzStorageBlob -Force
continue
}

if (!($blob | Get-Member 'BlobProperties')) {
continue
}

if ($blob.BlobProperties.LeaseState -eq 'Leased') {
Write-Host "Breaking blob lease: $($blob.Name), account: $($account.StorageAccountName), group: $($group.ResourceGroupName)"
$blob.ICloudBlob.BreakLease()
}
if (!$accounts) { break }

if ($blob.BlobProperties.HasLegalHold) {
Write-Host "Removing legal hold - blob: $($blob.Name), account: $($account.StorageAccountName), group: $($group.ResourceGroupName)"
$blob | Set-AzStorageBlobLegalHold -DisableLegalHold | Out-Null
}
}
} catch {
Write-Warning "Ensure user has 'Storage Blob Data Owner' RBAC permission on subscription or resource group"
Write-Error $_
throw
}
# Sometimes we get a 404 blob not found but can still delete containers,
# and sometimes we must delete the blob if there's a legal hold.
# Try to remove the blob, but keep running regardless.
$succeeded = $false
for ($attempt = 0; $attempt -lt 2; $attempt++) {
if ($succeeded) {
break
}

try {
foreach ($blob in $blobs) {
if ($blob.BlobProperties.ImmutabilityPolicy.PolicyMode) {
Write-Host "Removing immutability policy - blob: $($blob.Name), account: $($ctx.StorageAccountName), group: $($group.ResourceGroupName)"
$null = $blob | Remove-AzStorageBlobImmutabilityPolicy
}
}
}
catch {}

try {
foreach ($blob in $blobs) {
$blob | Remove-AzStorageBlob -Force
}
$succeeded = $true
}
catch {
Write-Warning "Failed to remove blobs - account: $($ctx.StorageAccountName), group: $($group.ResourceGroupName)"
Write-Warning $_
}
}

try {
# Use AzRm cmdlet as deletion will only work through ARM with the immutability policies defined on the blobs
$containers | ForEach-Object { Remove-AzRmStorageContainer -Name $_.Name -StorageAccountName $ctx.StorageAccountName -ResourceGroupName $group.ResourceGroupName -Force }
} catch {
Write-Warning "Container removal failed. Ignoring the error and trying to delete the storage account."
Write-Warning $_
}
Remove-AzStorageAccount -StorageAccountName $account.StorageAccountName -ResourceGroupName $account.ResourceGroupName -Force
}
}
if ($WhatIfPreference) {
Write-Host "What if: Removing resource group $($group.ResourceGroupName)"
}
else {
Remove-AzResourceGroup -ResourceGroupName $group.ResourceGroupName -Force -AsJob
foreach ($account in $accounts) {
RemoveStorageAccount -Account $account
}
}
}
Expand Down Expand Up @@ -401,6 +307,96 @@ function SetStorageNetworkAccessRules([string]$ResourceGroupName, [array]$AllowI
}
}

function RemoveStorageAccount($Account) {
Write-Host ($WhatIfPreference ? 'What if: ' : '') + "Readying $($Account.StorageAccountName) in $($Account.ResourceGroupName) for deletion"
# If it doesn't have containers then we can skip the explicit clean-up of this storage account
if ($Account.Kind -eq "FileStorage") { return }

$containers = New-AzStorageContext -StorageAccountName $Account.StorageAccountName | Get-AzStorageContainer
$blobs = $containers | Get-AzStorageBlob
$deleteNow = @()

try {
foreach ($blob in $blobs) {
$shouldDelete = EnableBlobDeletion -Blob $blob -StorageAccountName $Account.StorageAccountName -ResourceGroupName $Account.ResourceGroupName
if ($shouldDelete) {
$deleteNow += $blob
}
}
} catch {
Write-Warning "Ensure user has 'Storage Blob Data Owner' RBAC permission on subscription or resource group"
Write-Error $_
throw
}

# Blobs with legal holds or immutability policies must be deleted individually
# before the container/account can be deleted
foreach ($blobToDelete in $deleteNow) {
try {
$blobToDelete | Remove-AzStorageBlob -Force
} catch {
Write-Host "Blob removal failed: $($Blob.Name), account: $($Account.storageAccountName), group: $($Account.ResourceGroupName)"
Write-Warning "Ignoring the error and trying to delete the storage account"
Write-Warning $_
}
}

foreach ($container in $containers) {
if ($container.BlobContainerProperties.HasImmutableStorageWithVersioning) {
try {
# Use AzRm cmdlet as deletion will only work through ARM with the immutability policies defined on the blobs
Remove-AzRmStorageContainer -Name $container.Name -StorageAccountName $Account.StorageAccountName -ResourceGroupName $Account.ResourceGroupName -Force
#$container | Remove-AzStorageContainer
} catch {
Write-Host "Container removal failed: $($container.Name), account: $($Account.storageAccountName), group: $($Account.ResourceGroupName)"
Write-Warning "Ignoring the error and trying to delete the storage account"
Write-Warning $_
}
}
}

if ($containers) {
Remove-AzStorageAccount -StorageAccountName $Account.StorageAccountName -ResourceGroupName $Account.ResourceGroupName -Force
}
}

function EnableBlobDeletion($Blob, $StorageAccountName, $ResourceGroupName) {
# Some properties like immutability policies require the blob to be
# deleted before the container can be deleted
$forceBlobDeletion = $false

# We can't edit blobs with customer encryption without using that key
# so just try to delete them fully instead. It is unlikely they
# will also have a legal hold enabled.
if (($Blob | Get-Member 'ListBlobProperties') `
-and $Blob.ListBlobProperties.Properties.CustomerProvidedKeySha256) {
return $true
}

if (!($Blob | Get-Member 'BlobProperties')) {
return $false
}

if ($Blob.BlobProperties.ImmutabilityPolicy.PolicyMode) {
Write-Host "Removing immutability policy - blob: $($Blob.Name), account: $StorageAccountName, group: $ResourceGroupName"
$null = $Blob | Remove-AzStorageBlobImmutabilityPolicy
$forceBlobDeletion = $true
}

if ($Blob.BlobProperties.HasLegalHold) {
Write-Host "Removing legal hold - blob: $($Blob.Name), account: $StorageAccountName, group: $ResourceGroupName"
$Blob | Set-AzStorageBlobLegalHold -DisableLegalHold | Out-Null
$forceBlobDeletion = $true
}

if ($Blob.BlobProperties.LeaseState -eq 'Leased') {
Write-Host "Breaking blob lease: $($Blob.Name), account: $StorageAccountName, group: $ResourceGroupName"
$Blob.ICloudBlob.BreakLease()
}

return $forceBlobDeletion
}

function DoesSubnetOverlap([string]$ipOrCidr, [string]$overlapIp) {
[System.Net.IPAddress]$overlapIpAddress = $overlapIp
$parsed = $ipOrCidr -split '/'
Expand Down
5 changes: 3 additions & 2 deletions eng/pipelines/live-test-cleanup-template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ steps:
-Verbose `
${{ parameters.AdditionalParameters }} `
-UseExistingAzContext `
-GroupFilter '${{ parameters.GroupFilter }}' `
-WhatIf:$${{ parameters.DryRun }}

displayName: ${{ parameters.DisplayName }}
Expand All @@ -90,8 +91,8 @@ steps:
@subscriptionConfiguration `
-Verbose `
${{ parameters.AdditionalParameters }} `
-WhatIf:$${{ parameters.DryRun }} `
-GroupFilter '${{ parameters.GroupFilter }}'
-GroupFilter '${{ parameters.GroupFilter }}' `
-WhatIf:$${{ parameters.DryRun }}
displayName: ${{ parameters.DisplayName }}
continueOnError: true
env:
Expand Down