Skip to content

Commit af40a25

Browse files
feat: avm bicep (#121)
* feat: avm bicep * Fix role assignment check in cleanup script * make replacements dynamic for bicep * fix duplicate entries * max allowed is 10 * Decrease retry attempts * Fix order of installation * Add deployment cleanup * Adjust cleanup logic to happen before deployment * improve bicep templating * fix escaping * fix file name * linting * add default value capability with double pipe syntax * remove test output * fix e2e test --------- Co-authored-by: Zach Trocinski <ztrocinski@outlook.com>
1 parent 6f8a43b commit af40a25

17 files changed

Lines changed: 288 additions & 137 deletions

File tree

.config/ALZ-Powershell.config.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@
2727
"release_artifact_root_path": ".",
2828
"release_artifact_config_file": ".config/ALZ-Powershell.config.json"
2929
},
30+
"bicep": {
31+
"url": "https://github.com/Azure/alz-bicep-accelerator",
32+
"release_artifact_name": "starter_modules.zip",
33+
"release_artifact_root_path": ".",
34+
"release_artifact_config_file": ".config/ALZ-Powershell.config.json"
35+
},
3036
"bicep-classic": {
3137
"url": "https://github.com/Azure/ALZ-Bicep",
3238
"release_artifact_name": "accelerator.zip",

.github/tests/cleanup-scripts/cleanup_azure_resouces.ps1

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# This file can be used to clean up Resource Groups if there has been an issue with the End to End tests.
22
# CAUTION: Make sure you are connected to the correct subscription before running this script!
3+
4+
# Check for and install the resource-graph extension if not already installed
5+
$installedExtensions = az extension list --query "[].name" -o tsv
6+
if ($installedExtensions -notcontains "resource-graph") {
7+
Write-Host "Installing Azure CLI resource-graph extension..."
8+
az extension add --name resource-graph
9+
} else {
10+
Write-Host "Azure CLI resource-graph extension is already installed."
11+
}
12+
313
$managementGroupFilter = "alz-r"
414
if($managementGroupFilter -eq "")
515
{
@@ -70,13 +80,15 @@ $managementGroups | ForEach-Object -Parallel {
7080
} -ThrottleLimit 10
7181

7282
$roleDefinitionsFilter = $using:roleDefinitionsFilter
73-
$subscriptions = $using:subscriptions
83+
7484
$roleDefinitions = az role definition list --custom-role-only true --scope "/providers/Microsoft.Management/managementGroups/$managementGroup" --query "[].{name:name,roleName:roleName,id:id,assignableScopes:assignableScopes}" -o json | ConvertFrom-Json | Where-Object { $_.roleName -like "*$roleDefinitionsFilter*" -and $_.assignableScopes -contains "/providers/Microsoft.Management/managementGroups/$managementGroup" }
7585
$roleDefinitions | ForEach-Object -Parallel {
7686
$managementGroup = $using:managementGroup
7787
$roleDefinition = $_
7888

79-
$roleAssignments = az role assignment list --role $roleDefinition.name --scope "/providers/Microsoft.Management/managementGroups/$managementGroup" --query "[].{id:id,principalName:principalName,principalId:principalId}" -o json | ConvertFrom-Json
89+
Write-Host "$($roleDefinition.roleName) - $($managementGroup): Querying role assignments using Resource Graph for role definition $($roleDefinition.name)"
90+
$query = "authorizationresources | where type == 'microsoft.authorization/roleassignments' | where properties.roleDefinitionId == '/providers/Microsoft.Authorization/RoleDefinitions/$($roleDefinition.name)' | order by ['name'] asc"
91+
$roleAssignments = az graph query -q $query --query "data[].{id:id,principalId:properties.principalId}" -o json | ConvertFrom-Json
8092
$roleAssignments | ForEach-Object -Parallel {
8193
$managementGroup = $using:managementGroup
8294
$roleDefinition = $using:roleDefinition
@@ -85,19 +97,14 @@ $managementGroups | ForEach-Object -Parallel {
8597
az role assignment delete --ids $roleAssignment.id
8698
} -ThrottleLimit 10
8799

88-
foreach ($subscription in $using:subscriptions) {
89-
$subscriptionRoleAssignments = az role assignment list --role $roleDefinition.name --subscription $subscription --query "[].{id:id,principalName:principalName,principalId:principalId}" -o json | ConvertFrom-Json
90-
$subscriptionRoleAssignments | ForEach-Object -Parallel {
91-
$roleDefinition = $using:roleDefinition
92-
$subscription = $using:subscription
93-
$roleAssignment = $_
94-
Write-Host "Deleting role assignment: $($roleAssignment.id) for role definition: $($roleDefinition.roleName) in subscription: $subscription"
95-
az role assignment delete --ids $roleAssignment.id
96-
} -ThrottleLimit 10
97-
}
98-
99100
Write-Host "Deleting custom role definition: $($roleDefinition.roleName) in management group: $managementGroup"
100-
az role definition delete --name $roleDefinition.name --scope "/providers/Microsoft.Management/managementGroups/$managementGroup"
101+
$result = az role definition delete --name $roleDefinition.name --scope "/providers/Microsoft.Management/managementGroups/$managementGroup" 2>&1
102+
if($result -like "*ERROR*")
103+
{
104+
Write-Warning "Role definition $($roleDefinition.roleName) in management group: $managementGroup could not be deleted...$([Environment]::NewLine)$result"
105+
} else {
106+
Write-Host "Role definition $($roleDefinition.roleName) in management group: $managementGroup deleted successfully."
107+
}
101108

102109
} -ThrottleLimit 10
103110
} -ThrottleLimit 10

.github/workflows/end-to-end-test.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,10 @@ jobs:
310310
if($infrastructureAsCode -eq "bicep") {
311311
$Inputs["network_type"] = "none"
312312
$Inputs["intermediate_root_management_group_id"] = "alz-$uniqueId"
313+
$Inputs["management_group_id_prefix"] = ""
314+
$Inputs["management_group_id_postfix"] = ""
315+
$Inputs["management_group_name_prefix"] = ""
316+
$Inputs["management_group_name_postfix"] = ""
313317
}
314318
315319
$json = ConvertTo-Json $Inputs -Depth 100

alz/azuredevops/main.tf

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,6 @@ module "file_manipulation" {
116116
cd_template_file_name = local.cd_template_file_name
117117
pipeline_target_folder_name = local.target_folder_name
118118
bicep_parameters_file_path = var.bicep_parameters_file_path
119-
subscription_ids = var.subscription_ids
120-
root_parent_management_group_id = var.root_parent_management_group_id
121119
agent_pool_or_runner_configuration = local.agent_pool_or_runner_configuration
122120
pipeline_files_directory_path = local.pipeline_files_directory_path
123121
pipeline_template_files_directory_path = local.pipeline_template_files_directory_path

alz/azuredevops/pipelines/bicep/templates/cd-template.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ stages:
5050
parameters:
5151
parametersFileName: $(PARAMETERS_FILE_NAME)
5252

53+
- template: ./helpers/bicep-installer.yaml
54+
parameters:
55+
serviceConnection: '${service_connection_name_plan}'
56+
5357
- task: AzurePowerShell@5
5458
displayName: 'Check for First Deployment'
5559
inputs:
@@ -74,10 +78,6 @@ stages:
7478
7579
Write-Host "##vso[task.setvariable variable=FIRST_DEPLOYMENT;]$firstDeployment"
7680
77-
- template: ./helpers/bicep-installer.yaml
78-
parameters:
79-
serviceConnection: '${service_connection_name_plan}'
80-
8181
%{ for script_file in script_files ~}
8282
- $${{ if eq(parameters['${script_file.name}'], true) }}:
8383
- template: ./helpers/bicep-deploy.yaml

alz/azuredevops/pipelines/bicep/templates/ci-template.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ stages:
6666
parameters:
6767
parametersFileName: $(PARAMETERS_FILE_NAME)
6868

69+
- template: ./helpers/bicep-installer.yaml
70+
parameters:
71+
serviceConnection: '${service_connection_name_plan}'
72+
6973
- task: AzurePowerShell@5
7074
displayName: 'Check for First Deployment'
7175
inputs:
@@ -90,10 +94,6 @@ stages:
9094
9195
Write-Host "##vso[task.setvariable variable=FIRST_DEPLOYMENT;]$firstDeployment"
9296
93-
- template: ./helpers/bicep-installer.yaml
94-
parameters:
95-
serviceConnection: '${service_connection_name_plan}'
96-
9797
%{ for script_file in script_files ~}
9898
- template: ./helpers/bicep-deploy.yaml
9999
parameters:

alz/azuredevops/pipelines/bicep/templates/helpers/bicep-deploy.yaml

Lines changed: 74 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,14 @@ parameters:
3131
steps:
3232
- task: AzurePowerShell@5
3333
displayName: '$${{ parameters.displayName }}'
34+
retryCountOnTaskFailure: 10
3435
inputs:
3536
azureSubscription: $${{ parameters.serviceConnection }}
3637
azurePowerShellVersion: 'LatestVersion'
3738
pwsh: true
3839
ScriptType: 'InlineScript'
3940
Inline: |
40-
$deploymentType = "$${{ parameters.deploymentType }}"
41+
$deploymentType = "$${{ parameters.deploymentType }}"
4142
$whatIfEnabled = [System.Convert]::ToBoolean("$${{ parameters.whatIfEnabled }}")
4243
4344
# Check if we should skip what-if for first deployment
@@ -56,6 +57,17 @@ steps:
5657
exit 0
5758
}
5859
60+
# Check if this is a retry attempt and clean up previous deployments
61+
$isRetry = $env:AGENT_JOBATTEMPT -and [int]$env:AGENT_JOBATTEMPT -gt 1
62+
if ($isRetry) {
63+
Write-Host ""
64+
Write-Host "================================================" -ForegroundColor Yellow
65+
Write-Host "⚠ Retry Attempt Detected (Attempt #$($env:AGENT_JOBATTEMPT))" -ForegroundColor Yellow
66+
Write-Host "================================================" -ForegroundColor Yellow
67+
Write-Host "Cleaning up previous failed deployment..." -ForegroundColor Yellow
68+
Write-Host ""
69+
}
70+
5971
# Generate deployment name without timestamp for consistent deployment stack names
6072
$deploymentPrefix = "alz"
6173
@@ -165,7 +177,6 @@ steps:
165177
Write-Error "What-If validation failed: $($_.Exception.Message)"
166178
throw
167179
}
168-
169180
} else {
170181
# Deployment Stack mode - actual deployment
171182
$stackParameters = @{
@@ -178,82 +189,79 @@ steps:
178189
Verbose = $true
179190
}
180191
181-
$retryCount = 0
182-
$retryMax = 20
183-
$initialRetryDelay = 20
184-
$retryDelayIncrement = 10
185-
$finalSuccess = $false
192+
$result = $null
186193
187-
while ($retryCount -lt $retryMax) {
188-
if ($retryCount -gt 0) {
189-
$retryDelay = $initialRetryDelay + ($retryCount * $retryDelayIncrement)
190-
Write-Host "Retrying deployment stack after $retryDelay seconds..." -ForegroundColor Yellow
191-
Start-Sleep -Seconds $retryDelay
192-
Write-Host "Retry attempt $retryCount" -ForegroundColor Yellow
193-
}
194-
195-
$result = $null
194+
try {
195+
switch ($deploymentType) {
196+
"managementGroup" {
197+
$targetManagementGroupId = "$${{ parameters.managementGroupId }}"
198+
if ([string]::IsNullOrWhiteSpace($targetManagementGroupId)) {
199+
$targetManagementGroupId = (Get-AzContext).Tenant.TenantId
200+
}
196201
197-
try {
198-
switch ($deploymentType) {
199-
"managementGroup" {
200-
$targetManagementGroupId = "$${{ parameters.managementGroupId }}"
201-
if ([string]::IsNullOrWhiteSpace($targetManagementGroupId)) {
202-
$targetManagementGroupId = (Get-AzContext).Tenant.TenantId
202+
# Clean up all deployments before each deployment to avoid quota issues
203+
try {
204+
Write-Host "Cleaning up existing deployments in management group..." -ForegroundColor Cyan
205+
$allDeployments = Get-AzManagementGroupDeployment -ManagementGroupId $targetManagementGroupId -ErrorAction SilentlyContinue
206+
if ($allDeployments -and $allDeployments.Count -gt 0) {
207+
Write-Host "Found $($allDeployments.Count) deployment(s) to clean up" -ForegroundColor Yellow
208+
$batchSize = 200
209+
for ($i = 0; $i -lt $allDeployments.Count; $i += $batchSize) {
210+
$batch = $allDeployments | Select-Object -Skip $i -First $batchSize
211+
Write-Host " Deleting batch of $($batch.Count) deployments..." -ForegroundColor Gray
212+
$batch | ForEach-Object -Parallel {
213+
Remove-AzManagementGroupDeployment -ManagementGroupId $using:targetManagementGroupId -Name $_.DeploymentName -ErrorAction SilentlyContinue
214+
} -ThrottleLimit 100
215+
}
216+
Write-Host "✓ All deployments cleaned up" -ForegroundColor Green
217+
} else {
218+
Write-Host "No deployments to clean up" -ForegroundColor Green
203219
}
204-
205-
Write-Host "Creating Management Group Deployment Stack: $deploymentName" -ForegroundColor Cyan
206-
$result = New-AzManagementGroupDeploymentStack @stackParameters -ManagementGroupId $targetManagementGroupId -Location "$${{ parameters.location }}"
220+
} catch {
221+
Write-Warning "Could not clean up deployments: $($_.Exception.Message)"
207222
}
208-
"subscription" {
209-
if (-not [string]::IsNullOrWhiteSpace("$${{ parameters.subscriptionId }}")) {
210-
Write-Host "Setting subscription context to: $${{ parameters.subscriptionId }}" -ForegroundColor Cyan
211-
Select-AzSubscription -SubscriptionId "$${{ parameters.subscriptionId }}" | Out-Null
212-
}
213223
214-
Write-Host "Creating Subscription Deployment Stack: $deploymentName" -ForegroundColor Cyan
215-
$result = New-AzSubscriptionDeploymentStack @stackParameters -Location "$${{ parameters.location }}"
224+
Write-Host "Creating Management Group Deployment Stack: $deploymentName" -ForegroundColor Cyan
225+
$result = New-AzManagementGroupDeploymentStack @stackParameters -ManagementGroupId $targetManagementGroupId -Location "$${{ parameters.location }}"
226+
}
227+
"subscription" {
228+
if (-not [string]::IsNullOrWhiteSpace("$${{ parameters.subscriptionId }}")) {
229+
Write-Host "Setting subscription context to: $${{ parameters.subscriptionId }}" -ForegroundColor Cyan
230+
Select-AzSubscription -SubscriptionId "$${{ parameters.subscriptionId }}" | Out-Null
216231
}
217-
"resourceGroup" {
218-
if (-not [string]::IsNullOrWhiteSpace("$${{ parameters.subscriptionId }}")) {
219-
Write-Host "Setting subscription context to: $${{ parameters.subscriptionId }}" -ForegroundColor Cyan
220-
Select-AzSubscription -SubscriptionId "$${{ parameters.subscriptionId }}" | Out-Null
221-
}
222232
223-
Write-Host "Creating Resource Group Deployment Stack: $deploymentName" -ForegroundColor Cyan
224-
$result = New-AzResourceGroupDeploymentStack @stackParameters -ResourceGroupName "$${{ parameters.resourceGroupName }}"
225-
}
226-
default {
227-
Write-Error "Invalid deployment type: $deploymentType"
228-
throw "Invalid deployment type: $deploymentType"
229-
}
233+
Write-Host "Creating Subscription Deployment Stack: $deploymentName" -ForegroundColor Cyan
234+
$result = New-AzSubscriptionDeploymentStack @stackParameters -Location "$${{ parameters.location }}"
230235
}
236+
"resourceGroup" {
237+
if (-not [string]::IsNullOrWhiteSpace("$${{ parameters.subscriptionId }}")) {
238+
Write-Host "Setting subscription context to: $${{ parameters.subscriptionId }}" -ForegroundColor Cyan
239+
Select-AzSubscription -SubscriptionId "$${{ parameters.subscriptionId }}" | Out-Null
240+
}
231241
232-
if ($result.ProvisioningState -eq "Succeeded") {
233-
$finalSuccess = $true
234-
Write-Host ""
235-
Write-Host "================================================" -ForegroundColor Green
236-
Write-Host "✓ Deployment Stack Succeeded: $deploymentName" -ForegroundColor Green
237-
Write-Host "================================================" -ForegroundColor Green
238-
Write-Host ""
239-
break
240-
} else {
241-
Write-Warning "Deployment stack finished with state: $($result.ProvisioningState)"
242-
$retryCount++
242+
Write-Host "Creating Resource Group Deployment Stack: $deploymentName" -ForegroundColor Cyan
243+
$result = New-AzResourceGroupDeploymentStack @stackParameters -ResourceGroupName "$${{ parameters.resourceGroupName }}"
243244
}
244-
} catch {
245-
Write-Warning "Deployment stack failed with error: $($_.Exception.Message)"
246-
$retryCount++
247-
248-
if ($retryCount -ge $retryMax) {
249-
Write-Error "Deployment stack failed after $retryMax attempts"
250-
throw
245+
default {
246+
Write-Error "Invalid deployment type: $deploymentType"
247+
throw "Invalid deployment type: $deploymentType"
251248
}
252249
}
253-
}
254250
255-
if (-not $finalSuccess) {
256-
Write-Error "Deployment stack did not succeed after $retryMax attempts"
257-
throw "Deployment stack did not succeed after $retryMax attempts"
251+
if ($result.ProvisioningState -eq "Succeeded") {
252+
$finalSuccess = $true
253+
Write-Host ""
254+
Write-Host "================================================" -ForegroundColor Green
255+
Write-Host "✓ Deployment Stack Succeeded: $deploymentName" -ForegroundColor Green
256+
Write-Host "================================================" -ForegroundColor Green
257+
Write-Host ""
258+
break
259+
} else {
260+
Write-Error "Deployment stack finished with state: $($result.ProvisioningState)"
261+
exit 1
262+
}
263+
} catch {
264+
Write-Error "Deployment stack failed with error: $($_.Exception.Message)"
265+
exit 1
258266
}
259267
}

alz/azuredevops/variables.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,7 @@ variable "bicep_parameters_file_path" {
615615
This JSON file specifies configuration values for Azure Landing Zones resources.
616616
EOT
617617
type = string
618-
default = "parameters.json"
618+
default = "template-parameters.json"
619619
}
620620

621621
variable "custom_role_definitions_terraform" {

0 commit comments

Comments
 (0)