Skip to content
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
4 changes: 2 additions & 2 deletions src/AzOps.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#
# Generated by: Customer Architecture Team (CAT)
#
# Generated on: 05/23/2025
# Generated on: 6/10/2025
#

@{
Expand Down Expand Up @@ -52,7 +52,7 @@ PowerShellVersion = '7.2'

# Modules that must be imported into the global environment prior to importing this module
RequiredModules = @(@{ModuleName = 'PSFramework'; RequiredVersion = '1.12.346'; },
@{ModuleName = 'Az.Accounts'; RequiredVersion = '5.0.0'; },
@{ModuleName = 'Az.Accounts'; RequiredVersion = '5.1.0'; },
@{ModuleName = 'Az.Billing'; RequiredVersion = '2.2.0'; },
@{ModuleName = 'Az.ResourceGraph'; RequiredVersion = '1.2.1'; },
@{ModuleName = 'Az.Resources'; RequiredVersion = '8.0.0'; })
Expand Down
8 changes: 4 additions & 4 deletions src/internal/functions/New-AzOpsDeployment.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@
$parameters.ResultFormat = $WhatIfResultFormat
}
# Resource Groups excluding Microsoft.Resources/resourceGroups that needs to be submitted at subscription scope
if ($scopeObject.resourcegroup -and $TemplateObject.resources[0].type -ne 'Microsoft.Resources/resourceGroups') {
if ($scopeObject.Resourcegroup -and $TemplateObject.resources[0].type -ne 'Microsoft.Resources/resourceGroups') {
Set-AzOpsContext -ScopeObject $scopeObject
$whatIfCommand = 'Get-AzResourceGroupDeploymentWhatIfResult'
if ($null -ne $DeploymentStackSettings) {
Expand All @@ -166,7 +166,7 @@
$parameters.Remove('Location')
}
# Subscriptions
elseif ($scopeObject.subscription) {
elseif ($scopeObject.Subscription) {
Set-AzOpsContext -ScopeObject $scopeObject
$whatIfCommand = 'Get-AzSubscriptionDeploymentWhatIfResult'
if ($null -ne $DeploymentStackSettings) {
Expand All @@ -178,7 +178,7 @@
}
}
# Management Groups
elseif ($scopeObject.managementGroup -and (-not ($scopeObject.StatePath).StartsWith('azopsscope-assume-new-resource_'))) {
elseif ($scopeObject.ManagementGroup -and (-not ($scopeObject.StatePath).StartsWith('azopsscope-assume-new-resource_'))) {
$parameters.ManagementGroupId = $scopeObject.managementgroup
$whatIfCommand = 'Get-AzManagementGroupDeploymentWhatIfResult'
if ($null -ne $DeploymentStackSettings) {
Expand All @@ -196,7 +196,7 @@
$deploymentCommand = 'New-AzTenantDeployment'
}
# If Management Group resource was not found, validate and prepare for first time deployment of resource
elseif ($scopeObject.managementGroup -and (($scopeObject.StatePath).StartsWith('azopsscope-assume-new-resource_'))) {
elseif ($scopeObject.ManagementGroup -and (($scopeObject.StatePath).StartsWith('azopsscope-assume-new-resource_'))) {
$resourceScopeFileContent = Get-Content -Path $addition | ConvertFrom-Json -Depth 100
$resource = ($resourceScopeFileContent.resources | Where-Object {$_.type -eq 'Microsoft.Management/managementGroups'} | Select-Object -First 1)
$pathDir = (Get-Item -Path $addition -Force).Directory | Resolve-Path -Relative
Expand Down
74 changes: 63 additions & 11 deletions src/internal/functions/Remove-AzResourceRaw.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,40 @@
}
}
}
function Get-AzOpsDeploymentStackActionOnUnmanage {
param (
[string]
$ResourcesCleanupAction,
[string]
$ResourceGroupsCleanupAction,
[string]
$ManagementGroupsCleanupAction
)

# Check for deleteAll pattern
if ($ResourcesCleanupAction -eq 'delete' -and
$ResourceGroupsCleanupAction -eq 'delete' -and
$ManagementGroupsCleanupAction -eq 'delete') {
return 'deleteAll'
}

# Check for deleteResources pattern
if ($ResourcesCleanupAction -eq 'delete' -and
$ResourceGroupsCleanupAction -eq 'detach' -and
$ManagementGroupsCleanupAction -eq 'detach') {
return 'deleteResources'
}

# Check for detachAll pattern
if ($ResourcesCleanupAction -eq 'detach' -and
$ResourceGroupsCleanupAction -eq 'detach' -and
$ManagementGroupsCleanupAction -eq 'detach') {
return 'detachAll'
}

# Default fallback
return 'detachAll'
}
if ($null -ne $InputObject -and $Recursive) {
# Perform recursive resource deletion
$result = Remove-AzResourceRawRecursive -InputObject $InputObject
Expand Down Expand Up @@ -159,18 +193,36 @@
try {
# Set Azure context for removal operation
Set-AzOpsContext -ScopeObject $ScopeObject
$null = Remove-AzResource -ResourceId $ScopeObject.Scope -Force -ErrorAction Stop
$maxAttempts = 4
$attempt = 1
$gone = $false
while ($gone -eq $false -and $attempt -le $maxAttempts) {
Write-AzOpsMessage -LogLevel InternalComment -LogString 'Remove-AzResourceRaw.Resource.CheckExistence' -LogStringValues $ScopeObject.Scope
Start-Sleep -Seconds 10
$tryResource = Get-AzOpsResource -ScopeObject $ScopeObject -ErrorAction SilentlyContinue
if (-not $tryResource) {
$gone = $true
# Evaluate the resource type and perform the appropriate removal action
if ($ScopeObject.Resource -eq 'deploymentStacks') {
$actionOnUnmanage = Get-AzOpsDeploymentStackActionOnUnmanage -ResourcesCleanupAction $resource.resourcesCleanupAction -ResourceGroupsCleanupAction $resource.resourceGroupsCleanupAction -ManagementGroupsCleanupAction $resource.managementGroupsCleanupAction
if ($ScopeObject.ResourceGroup) {
$removeCommand = 'Remove-AzResourceGroupDeploymentStack'
}
elseif ($ScopeObject.Subscription) {
$removeCommand = 'Remove-AzSubscriptionDeploymentStack'
}
elseif ($scopeObject.ManagementGroup) {
$removeCommand = 'Remove-AzManagementGroupDeploymentStack'
}
Write-AzOpsMessage -LogLevel Verbose -LogString 'Remove-AzResourceRaw.Resource.StackCommand' -LogStringValues $removeCommand, $ScopeObject.Scope, $actionOnUnmanage
$null = & $removeCommand -ResourceId $ScopeObject.Scope -ActionOnUnmanage $actionOnUnmanage -Force -ErrorAction Stop
}
else {
Write-AzOpsMessage -LogLevel Verbose -LogString 'Remove-AzResourceRaw.Resource.Command' -LogStringValues 'Remove-AzResource', $ScopeObject.Scope
$null = Remove-AzResource -ResourceId $ScopeObject.Scope -Force -ErrorAction Stop
$maxAttempts = 4
$attempt = 1
$gone = $false
while ($gone -eq $false -and $attempt -le $maxAttempts) {
Write-AzOpsMessage -LogLevel InternalComment -LogString 'Remove-AzResourceRaw.Resource.CheckExistence' -LogStringValues $ScopeObject.Scope
Start-Sleep -Seconds 10
$tryResource = Get-AzOpsResource -ScopeObject $ScopeObject -ErrorAction SilentlyContinue
if (-not $tryResource) {
$gone = $true
}
$attempt++
}
$attempt++
}
}
catch {
Expand Down
2 changes: 2 additions & 0 deletions src/localized/en-us/Strings.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,8 @@
'Remove-AzResourceRaw.Resource.Recursive.Missing' = 'Missing required parameter InputObject, when running Recursive'#
'Remove-AzResourceRaw.Resource.Missing' = 'Missing required parameter ScopeObject'#
'Remove-AzResourceRaw.Resource.CheckExistence' = 'Checking existence after deletion of: [{0}]'# $FullyQualifiedResourceId
'Remove-AzResourceRaw.Resource.StackCommand' = 'Running deletion command: [{0}] for resource: [{1}] with ActionOnUnmanage: [{2}]' # $removeCommand, $ScopeObject.Scope, $actionOnUnmanage
'Remove-AzResourceRaw.Resource.Command' = 'Running deletion command: [{0}] for resource: [{1}]' # 'Remove-AzResource', $ScopeObject.Scope
'Remove-AzResourceRaw.Resource.Failed' = 'Unable to delete resource of type {0} with id {1}'# $ScopeObject.Resource, $ScopeObject.Scope
'Remove-AzResourceRawRecursive.Processing' = 'Recursive retry processing to delete resource of type {0} with id {1}'# $item.ScopeObject.Resource, $item.ScopeObject.Scope

Expand Down
13 changes: 11 additions & 2 deletions src/tests/integration/Repository.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -1372,7 +1372,7 @@ Describe "Repository" {
"A`t$($script:deployStacksAtRgScope.FullName[1])"
)
{Invoke-AzOpsPush -ChangeSet $changeSet} | Should -Not -Throw
Get-AzResourceGroupDeploymentStack -ResourceId "/subscriptions/$script:subscriptionId/resourceGroups/$($script:resourceGroupStacksDeploy.ResourceGroupName)/providers/Microsoft.Resources/deploymentStacks/AzOps-deploystacksatrg-$deploymentLocationId" -ErrorAction SilentlyContinue | Should -Not -Be $null
$resources = Get-AzResourceGroupDeploymentStack -ResourceId "/subscriptions/$script:subscriptionId/resourceGroups/$($script:resourceGroupStacksDeploy.ResourceGroupName)/providers/Microsoft.Resources/deploymentStacks/AzOps-deploystacksatrg-$deploymentLocationId" -ErrorAction SilentlyContinue | Should -Not -Be $null
Start-Sleep -Seconds 10
$changeSet = @(
"D`t$($script:deployStacksAtRgScope.FullName[0])",
Expand All @@ -1388,6 +1388,9 @@ Describe "Repository" {
Set-PSFConfig -FullName AzOps.Core.CustomTemplateResourceDeletion -Value $false
Start-Sleep -Seconds 30
Get-AzResourceGroupDeploymentStack -ResourceId "/subscriptions/$script:subscriptionId/resourceGroups/$($script:resourceGroupStacksDeploy.ResourceGroupName)/providers/Microsoft.Resources/deploymentStacks/AzOps-deploystacksatrg-$deploymentLocationId" -ErrorAction SilentlyContinue | Should -Be $null
foreach ($resource in $resources.Resources.Id) {
Get-AzResource -ResourceId $resource -ErrorAction SilentlyContinue | Should -Be $null
}
}
It "Deploy and Delete AzOps Managed DeploymentStacks at Subscription Scope with parameter file and exclusion of direct stack, expecting usage of parent stack" {
Set-PSFConfig -FullName AzOps.Core.CustomTemplateResourceDeletion -Value $true
Expand Down Expand Up @@ -1422,6 +1425,9 @@ Describe "Repository" {
Set-PSFConfig -FullName AzOps.Core.CustomTemplateResourceDeletion -Value $false
Start-Sleep -Seconds 30
Get-AzSubscriptionDeploymentStack -ResourceId "/subscriptions/$script:subscriptionId/providers/Microsoft.Resources/deploymentStacks/AzOps-deploystacksatsub-$deploymentLocationId" -ErrorAction SilentlyContinue | Should -Be $null
foreach ($resource in $stackConfig.Resources.Id) {
Get-AzResource -ResourceId $resource -ErrorAction SilentlyContinue | Should -Be $null
}
}
It "Deploy and Delete AzOps Managed DeploymentStacks at ManagementGroup Scope with multiparameter filename and parameter specific stackfile discovered with reverse lookup" {
Set-PSFConfig -FullName AzOps.Core.CustomTemplateResourceDeletion -Value $true
Expand All @@ -1432,7 +1438,7 @@ Describe "Repository" {
"A`t$($script:deployStacksAtMgScope.FullName[2])"
)
{Invoke-AzOpsPush -ChangeSet $changeSet} | Should -Not -Throw
Get-AzManagementGroupDeploymentStack -ResourceId "/providers/Microsoft.Management/managementGroups/$($script:managementManagementGroup.Name)/providers/Microsoft.Resources/deploymentStacks/AzOps-deploystacksatmg.x1-$deploymentLocationId" -ErrorAction SilentlyContinue | Should -Not -Be $null
$resources = Get-AzManagementGroupDeploymentStack -ResourceId "/providers/Microsoft.Management/managementGroups/$($script:managementManagementGroup.Name)/providers/Microsoft.Resources/deploymentStacks/AzOps-deploystacksatmg.x1-$deploymentLocationId" -ErrorAction SilentlyContinue | Should -Not -Be $null
Start-Sleep -Seconds 10
$changeSet = @(
"D`t$($script:deployStacksAtMgScope.FullName[2])"
Expand All @@ -1446,6 +1452,9 @@ Describe "Repository" {
Set-PSFConfig -FullName AzOps.Core.DeployAllMultipleTemplateParameterFiles -Value $false
Start-Sleep -Seconds 30
Get-AzManagementGroupDeploymentStack -ResourceId "/providers/Microsoft.Management/managementGroups/$($script:managementManagementGroup.Name)/providers/Microsoft.Resources/deploymentStacks/AzOps-deploystacksatmg.x1-$deploymentLocationId" -ErrorAction SilentlyContinue | Should -Be $null
foreach ($resource in $resources.Resources.Id) {
Get-AzResource -ResourceId $resource -ErrorAction SilentlyContinue | Should -Be $null
}
}
#endregion
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"actionOnUnmanage": "DeleteAll",
"actionOnUnmanage": "deleteAll",
"bypassStackOutOfSyncError": true,
"denySettingsMode": "None",
"denySettingsMode": "none",
"excludedAzOpsFiles": []
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"actionOnUnmanage": "deleteResources",
"bypassStackOutOfSyncError": true,
"denySettingsMode": "DenyDelete",
"denySettingsMode": "denyDelete",
"excludedAzOpsFiles": []
}
4 changes: 2 additions & 2 deletions src/tests/templates/deploystacksatsub.deploymentStacks.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"actionOnUnmanage": "DetachAll",
"actionOnUnmanage": "detachAll",
"bypassStackOutOfSyncError": true,
"denySettingsMode": "DenyDelete",
"denySettingsMode": "denyDelete",
"excludedAzOpsFiles": [
"deploystacksatsub.bicep"
]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"actionOnUnmanage": "DeleteAll",
"actionOnUnmanage": "deleteAll",
"bypassStackOutOfSyncError": true,
"denySettingsMode": "DenyDelete",
"denySettingsMode": "denyDelete",
"excludedAzOpsFiles": []
}