This is the YAML File which defined the main Pipeline while this Docs your are reading have been deployed. Most Parts of it are referencing Templates which are located bellow the subfolder /azure-pipelines.
I had the Idea to use this folder for pipeline Stage-Templates, then use the azure-pipelines/jobs subfolder for job-templates and finally a subfolder azure-pipelines/jobs/steps for, you guessed it, step-templates. But at the moment I am thinking to have another subfolder-stage azure-pipelines/stages/jobs/steps, and store pipelines directly in azure-pipelines, which atm are stored in azure-pipelines/triggers which feels kind of wrong.
trigger:batch:truebranches:include:-refs/heads/mainpr:-mainresources:repositories:-repository:selfendpoint:Mauwiivariables:-template:azure-pipelines/variables/default.yml-${{ if in(variables['Build.SourceBranch'], 'refs/heads/main', 'refs/heads/stable') }}:-template:azure-pipelines/variables/${{ variables['Build.SourceBranchName'] }}.ymlparameters:-name:bicepDirdisplayName:Directory containing Folders with Bicep Templatestype:stringdefault:'$(bicepDir)'-name:bicepParameterdisplayName:Used in some templates to change Resource Name or other variablestype:stringvalues:-dev-stg-proddefault:dev-name:resourceGroupNamedisplayName:Name of the Resource Group where templates will be deployed totype:stringdefault:'$(resourceGroupName)'-name:azureSubscriptiondisplayName:Name of the ARM-Service Connectiontype:stringdefault:'$(azureSubscription)'-name:locationdisplayName:Location where the Resources will be deployedtype:stringdefault:$(location)-name:agentpooldisplayName:Agent-Pool to be usedvalues:-'AzurePipelines'-'local'default:'AzurePipelines'extends:template:azure-pipelines/stages/main.ymlparameters:bicepDir:${{ parameters.bicepDir }}bicepParameter:${{ parameters.bicepParameter }}resourceGroupName:${{ parameters.resourceGroupName }}azureSubscription:${{ parameters.azureSubscription }}location:${{ parameters.location }}agentpool:${{ parameters.agentpool }}
This stage-template contains is defining the complete pipeline. I had it separated from azure-pipelines.yml to be able to run validate_pr.yml (pull request validation) with the same stages than azure-pipelines.yml, but after development moved on I got rid of validate_pr.yml but thought it could still be practical later on to have this main.yml template.
parameters:-name:bicepDirdisplayName:Directory containing Folders with Bicep Templatestype:stringdefault:IaC/bicep/deploy-name:bicepParameterdisplayName:Used in some templates to change Resource Name or other variablestype:stringvalues:-dev-stg-proddefault:dev-name:resourceGroupNamedisplayName:Name of the Resource Group where templates will be deployed totype:stringdefault:$(resourceGroupName)-name:azureSubscriptiondisplayName:Name of the ARM-Service Connectiontype:stringdefault:$(azureSubscription)-name:locationdisplayName:Location where the Resources will be deployed totype:stringdefault:'westeurope'-name:agentpooldisplayName:Agent-Pool to be usedvalues:-'AzurePipelines'-'local'default:'AzurePipelines'stages:-template:bicep_stage.ymlparameters:bicepDir:${{ parameters.bicepDir }}bicepParameter:${{ parameters.bicepParameter }}resourceGroupName:${{ parameters.resourceGroupName }}azureSubscription:${{ parameters.azureSubscription }}location:${{ parameters.location }}agentpool:${{ parameters.agentpool }}# - ${{ if in(variables['Build.SourceBranchName'], 'main', 'stable') }}:# - template: cleanup.yml# parameters:# agentpool: ${{ parameters.agentpool }}# resourceGroupName: ${{ parameters.resourceGroupName }}
This stage is seperated in two jobs. where the first job is enumerating how often the second job needs to be run. Well, ok, this sounds a bit weird, but the secret is that it is not re-running the second job for x-times, but for every folder found in IaC/bicep/deploy. The steps which will then be done are found in the next section
parameters:-name:bicepDirdisplayName:Directory containing Folders with Bicep Templatestype:string-name:bicepParameterdisplayName:Used in some templates to change Resource Name or other variablestype:stringvalues:-dev-stg-prod-name:resourceGroupNamedisplayName:Name of the Resource Group where templates will be deployed totype:string-name:azureSubscriptiondisplayName:Name of the ARM-Service Connectiontype:string-name:locationdisplayName:Location where the Resources will be deployed totype:string-name:agentpooldisplayName:Agent-Pool to be usedvalues:-'AzurePipelines'-'local'default:'AzurePipelines'stages:-stage:bicepdisplayName:Biceppool:name:${{ parameters.agentpool }}jobs:-job:getTemplateFoldersdisplayName:Get Template Folderssteps:-task:PowerShell@2name:mtrxenv:bicepDir:${{ parameters.bicepDir }}bicepParameter:${{ parameters.bicepParameter }}resourceGroupName:${{ parameters.resourceGroupName }}azureSubscription:${{ parameters.azureSubscription }}location:${{ parameters.location }}inputs:targetType:filePathfilePath:scripts/New-BicepMatrix.ps1-job:runnerdisplayName:Test and DeploydependsOn:getTemplateFolderscondition:succeeded()strategy:matrix:$[ dependencies.getTemplateFolders.outputs['mtrx.legs'] ]maxParallel:1steps:-template:jobs/steps/bicep_steps.ymlparameters:bicepDir:$(bicepDir)bicepTemplateDir:$(bicepTemplateDir)bicepParameter:$(bicepParameter)resourceGroupName:$(resourceGroupName)azureSubscription:$(azureSubscription)location:$(location)
trigger:paths:include:-docs-mkdocs.yml-azure-pipelines/mkdocs-material.yml-azure-pipelines/stages/jobs/steps/build_mkdocs.yml-scripts/deploy-mike.sh-src/requirements-mkdocs-material.txt-src/mkdocs-materialpr:-mainparameters:-name:pythonVersiondisplayName:Python Version to use when building MkDocs-Materialtype:stringvalues:-'3.9'-'3.10'default:'3.9'-name:mkdocsSiteDirdisplayName:Name of the Directory where MkDocs will be built totype:stringdefault:'site'-name:agentpooldisplayName:Agent-Pool to be usedvalues:-'AzurePipelines'-'local'default:'AzurePipelines'variables:-template:variables/default.ymljobs:-job:displayName:MkDocs-Materialpool:name:${{ parameters.agentpool }}steps:-bash:'export'displayName:'debugexport'-template:stages/jobs/steps/checkout_submodules.ymlparameters:submodule:'src/mkdocs-material'checkoutSelf:true-template:stages/jobs/steps/build_mkdocs.ymlparameters:pythonVersion:'${{parameters.pythonVersion}}'mkdocsSiteDir:'${{parameters.mkdocsSiteDir}}'-task:Bash@3displayName:'updategh-pages'inputs:targetType:filePathfilePath:scripts/deploy-mike.sh
trigger:branches:include:-refs/heads/main-update/*-feature/*-issue/*paths:include:-azure-pipelines/devopsbuildagent.yml-azure-pipelines/stages/jobs/build_devopsbuildagent_job.yml-src/DevOpsBuildAgentschedules:-cron:'00***'displayName:Nightly Buildalways:truebranches:include:-mainpr:nonevariables:-name:baseOSvalue:'linux'-name:baseDistrovalue:'ubuntu'-name:baseVersionvalue:'20.04'-name:dockerRegistryvalue:'docker.io'-name:dockerhubuservalue:'mauwii'-name:dockerimagevalue:'devopsbuildagent'parameters:-name:matrixtype:objectdisplayName:Object which defines the matrix strategydefault:amd64:baseArch:'amd64'targetProc:'x64'arm64v8:baseArch:'arm64v8'targetProc:'arm64'-name:maxParallelJobstype:numberdisplayName:Defines how many Jobs can be running in parallel by the matrix strategydefault:2stages:-stage:buildjobs:-template:stages/jobs/build_devopsbuildagent_job.ymlparameters:matrix:${{ parameters.matrix }}maxParallelJobs:${{ parameters.maxParallelJobs }}-stage:manifestdependsOn:buildjobs:-template:stages/jobs/pushManifest_job.ymlparameters:matrix:${{ parameters.matrix }}maxParallelJobs:${{ parameters.maxParallelJobs }}
Necessary to use this Pipeline is a Service Principal with permission to delete Resources and Resource Locks
Besides a Service Connection in DevOps, add Secret Variables to the Pipeline for:
ApplicationId
ClientSecret
SpTenantId
This Pipeline will delete Resources in Subscriptions of the used Service Principal automatically. It differs between Resources which where available before and after a initial Date, which will have a defined range of days from creation before they will be deleted. While date is not reach, it will set a Tag on the Resource with the deletion date (which is just used for information). After the Resource has reached the defined age it will be deleted.
param([Switch]$WhatIf,[Int16]$NewerResourceDays,[Int16]$OlderResourceDays)# Days until Resources get deletedif(-not($NewerResourceDays)){$NewerResourceDays=7}if(-not($OlderResourceDays)){$OlderResourceDays=14}# Set Initial Date$InitialDate=(Get-Date`-Year2022`-Month06`-Day15).ToUniversalTime()# Connect to Azure-CLI as Service Principal[void](azlogin`--service-principal`-u$env:ApplicationId`-p$env:ClientSecret`-t$env:SpTenantId)# Get Current UTC-Time$CurrentUTCtime=(Get-Date).ToUniversalTime()# Get Resources to be excluded$cleanupexclusion=(Get-Content`-Path./cleanupexclusion.json|ConvertFrom-Json)# Initialize Variables to count existing and deleted Resources/RGs$AllAzRgCount=0$DeletedAzRgCount=0$AllAzResourceCount=0$DeletedAzResourceCount=0# Function to Print InformationfunctionWrite-Info{param([System.String]$Title,[System.String]$Value,[Switch]$InitialNewLine,[Switch]$FinalNewLine)$Title="${Title}: "$Value="`t${Value}"if($InitialNewLine){$Title="`n${Title}"}if($FinalNewLine){$Value="${Value}`n"}Write-Host`-ForegroundColorCyan`-NoNewline`$TitleWrite-Host$Value}# Get Azure Subscriptions$AzSubscriptions=(Get-AzSubscription)# Iterate over Subscriptionsforeach($AzSubscriptionin$AzSubscriptions){# Set Context to current Subscription[void](Set-AzContext`-Subscription$AzSubscription.Id)[void](azaccountset `--subscription$AzSubscription.Id)# Get Resourcegroups of current Context$AzResourceGroups=(Get-AzResourceGroup)# Add Number of ResourceGroups to AllAzRgCount$AllAzRgCount+=($AzResourceGroups).Length# Get Number of Resources in current Context$AzResourceCount=(Get-AzResource).Length# Add Number of Resources in Subscription to AllAzResourceCount$AllAzResourceCount+=$AzResourceCount# Write Info to Host about current SubscriptionWrite-Info`-InitialNewLine`-Title"Subscription Name"`-Value$AzSubscription.NameWrite-Info`-Title"Resourcegroups"`-Value$AzResourceGroups.LengthWrite-Info`-Title"Resource Count"`-Value"$AzResourceCount"`-FinalNewLine# Iterate over Resource Groupsforeach($AzResourceGroupin$AzResourceGroups){# Get Resources in current Resource Group$AzResources=Get-AzResource`-ResourceGroupName$AzResourceGroup.ResourceGroupName# Write Info to Host about Current Resource GroupWrite-Info`-Title"Resource Group"`-Value$AzResourceGroup.ResourceGroupNameWrite-Info`-Title"Resource Count"`-Value$AzResources.Length# Iterate over Resources in current Resource Group$AzResources|ForEach-Object-Parallel{# Check if Resource should be excludedif($using:cleanupexclusion.ResourceTypes-contains$_.Type `-or$using:cleanupexclusion.ResourceIDs-contains$_.ResourceId){Write-Host"Skipping"$_.ResourceName# Create/update Tag$Tag=@{"DeletionDate"="skipped";}[void](Update-AzTag`-ResourceId$_.Id`-Tag$Tag`-OperationMerge`-ErrorAction:SilentlyContinue`-WhatIf:$using:WhatIf)}else{# Get Current Resource Creation Time$AzCurrentResource=(azresourcelist`--location$_.Location`--name$_.Name`--query"[].{Name:name, RG:resourceGroup, Created:createdTime, Changed:changedTime}"`-ojson|ConvertFrom-Json)# Check if Resource was created before or after initial date to give devs more days to react on older resourcesif(($AzCurrentResource.Created).ToUniversalTime()-gt$using:InitialDate){$DaysToDelete=($using:NewerResourceDays-($using:CurrentUTCtime-($AzCurrentResource.Created).ToUniversalTime()).Days)}else{$DaysToDelete=($using:OlderResourceDays-($using:CurrentUTCtime-$using:InitialDate).Days)}# Add/update Tag "DeletionDate" of Resource, or Delete it if defined age has been reachedif($DaysToDelete-gt0){# Write Info to Host when Resource will be deletedWrite-Host($AzCurrentResource.Name,"will get deleted on",(Get-Date).AddDays($DaysToDelete).ToString("yyyy-MM-dd"))# Create Tag$Tag=@{"DeletionDate"="$((Get-Date).AddDays($DaysToDelete).ToUniversalTime().ToString("yyyy-MM-dd"))";}[void](Update-AzTag`-ResourceId$_.Id`-Tag$Tag`-OperationMerge`-ErrorAction:SilentlyContinue`-WhatIf:$using:WhatIf)}else{# Get Resource Lock$AzResourceLock=(Get-AzResourceLock`-ResourceName$_.Name`-ResourceType$_.Type `-ResourceGroupName$_.ResourceGroupName)# Remove Resource Lock if existingif($AzResourceLock){Write-Host"Deleting Resource Lock of $($_.Name)"[void](Remove-AzResourceLock`-LockId$AzResourceLock.LockId`-Force:$true`-WhatIf:$using:WhatIf)}# Remove Resource$RmResource=Remove-AzResource`-ResourceId$_.Id`-WhatIf:$using:WhatIf`-ErrorAction:SilentlyContinue`-Force:$true# Write Info and Increment Deleted Resource Count if succeededif($RmResource){Write-Host"Deleted $($_.Name)"}else{Write-Host"Could not delete $($_.Name)"}}}}# Get Resourcecount in current Resource Group$AzRgResourceCountPostDeletion=(Get-AzResource`-ResourceGroupName$AzResourceGroup.ResourceGroupName).Length# Delete Resourcegroup if emptyif($AzRgResourceCountPostDeletion-eq0){[void](Remove-AzResourceGroup`-Name$AzResourceGroup.ResourceGroupName`-Force:$true`-WhatIf:$WhatIf)# Write Info to Host that ResourceGroup was deletedWrite-Info`-Title"Deleted Resourcegroup"`-Value$AzResourceGroup.ResourceGroupName`-FinalNewLine# Increment DeletedAzRgCount$DeletedAzRgCount++}else{# Write Info to Host that ResourceGroup is doneWrite-Info`-Title"Resourcegroup Done"`-Value$AzResourceGroup.ResourceGroupName`-FinalNewLine}}$AzResourceCountPostDeletion=(Get-AzResource).Length$DeletedAzResourceCount+=$AzResourceCount-$AzResourceCountPostDeletion# Write Info to Host that Subscription is doneWrite-Info`-Title"Subscription Done"`-Value$AzSubscription.Name`-FinalNewLine}# Write Info to Host about Resource-CountsWrite-Info`-Title"Deleted RGs"`-Value"`t$DeletedAzRgCount of $AllAzRgCount"Write-Info`-Title"Deleted resources"`-Value"$DeletedAzResourceCount of $AllAzResourceCount"
scripts/cleanupexclusion.json
This is a configuration File for ResourceTypes/ResourceIDs which should be excluded.