utilities/tools/ConvertTo-ARMTemplate.ps1 (201 lines of code) (raw):
<#
.SYNOPSIS
This script converts the module library from bicep to json based ARM templates.
.DESCRIPTION
The script finds all 'main.bicep' files and converts them to json based ARM templates
by using the following steps:
1. Remove existing main.json files (unless RemoveExistingTemplates is disabled)
2. Convert bicep files to json
3. Remove Bicep metadata from json (unless SkipMetadataCleanup is enabled)
4. Remove bicep files and folders (unless SkipBicepCleanUp is enabled)
5. Update workflow files - Replace .bicep with .json in workflow files (unless SkipPipelineUpdate is enabled)
.PARAMETER RootPath
Optional. The full path to the root of the repository.
.PARAMETER ModulePath
Optional. The relative path to the folder hosting bicep templates to convert. Defaults to the whole library.
.PARAMETER SkipTest
Optional. Skip convertion of 'main.test.bicep' files.
.PARAMETER SkipMetadataCleanup
Optional. Skip cleanup of Bicep metadata from json files.
.PARAMETER SkipBicepCleanUp
Optional. Skip removal of bicep files and folders.
.PARAMETER SkipPipelineUpdate
Optional. Skip replacing .bicep with .json in workflow files.
.PARAMETER RemoveExistingTemplates
Optional. Remove existing 'main.json' and 'main.test.json' files in the given path.
.PARAMETER RunSynchronous
Optional. Don't run the code using multiple threads. May be necessary if context does not support it
.EXAMPLE
ConvertTo-ARMTemplate
Converts bicep modules to json-based ARM template, cleaning up all bicep files and folders and updating the workflow files to use the json files.
.EXAMPLE
ConvertTo-ARMTemplate -RemoveExistingTemplates -SkipMetadataCleanup -SkipBicepCleanUp -SkipPipelineUpdate -SkipTest -Verbose
Regenerates compiled 'main.json' for the whole library.
.EXAMPLE
ConvertTo-ARMTemplate -ModuleRelativePath "modules\desktop-virtualization\application-groups" -RemoveExistingTemplates -SkipMetadataCleanup -SkipBicepCleanUp -SkipPipelineUpdate -SkipTest -Verbose
Regenerates compiled 'main.json' for the provided ModuleRelativePath folder.
#>
function ConvertTo-ARMTemplate {
[CmdletBinding(SupportsShouldProcess = $true)]
param (
[Parameter(Mandatory = $false)]
[string] $RootPath = (Get-Item $PSScriptRoot).Parent.Parent.FullName,
[Parameter(Mandatory = $false)]
[string] $ModuleRelativePath = 'modules',
[Parameter(Mandatory = $false)]
[switch] $SkipTest,
[Parameter(Mandatory = $false)]
[switch] $SkipMetadataCleanup,
[Parameter(Mandatory = $false)]
[switch] $SkipBicepCleanUp,
[Parameter(Mandatory = $false)]
[switch] $SkipPipelineUpdate,
[Parameter(Mandatory = $false)]
[switch] $RemoveExistingTemplates,
[Parameter(Mandatory = $false)]
[switch] $RunSynchronous
)
$Path = Join-Path $RootPath $ModuleRelativePath
if (-not $SkipTest) {
$BicepFilesToConvert = (Get-ChildItem -Path $Path -Include @('main.bicep', 'main.test.bicep') -Recurse -Force).FullName
} else {
$BicepFilesToConvert = (Get-ChildItem -Path $Path -Include @('main.bicep') -Recurse -Force).FullName
}
#region Remove existing main.json and main.test.bicep files
if ($RemoveExistingTemplates) {
$JsonFilesToRemove = (Get-ChildItem -Path $Path -Include @('main.json', 'main.test.json') -Recurse -Force -File).FullName
Write-Verbose "# Remove existing [main.json / main.test.json] files - Remove [$($JsonFilesToRemove.count)] file(s)"
foreach ($jsonFileToRemove in $JsonFilesToRemove) {
if ($PSCmdlet.ShouldProcess(('JSON File in Path [{0}]' -f (($jsonFileToRemove -replace '\\', '/') -split '/modules/')[1]), 'Remove')) {
$null = Remove-Item -Path $jsonFileToRemove -Force
}
Write-Verbose ' Remove existing main.json and main.test.json files - Done'
}
}
#endregion
#region Convert bicep files to json
Write-Verbose "# Convert bicep files to json - Processing [$($BicepFilesToConvert.count)] file(s)"
$buildScriptBlock = {
if (-not $RemoveExistingTemplates) {
Write-Verbose " Building template [$_]" -Verbose
bicep build $_
} else {
Write-Verbose " Template [$_] already existing. Skipping overwriting." -Verbose
}
}
if ($PSCmdlet.ShouldProcess(('Bicep [{0}] Templates' -f ($BicepFilesToConvert.count)), 'bicep build')) {
if ($RunSynchronous) {
$BicepFilesToConvert | ForEach-Object $buildScriptBlock
} else {
$BicepFilesToConvert | ForEach-Object -ThrottleLimit 4 -Parallel $buildScriptBlock
}
}
Write-Verbose ' Convert bicep files to json - Done'
#endregion
#region Remove Bicep metadata `_generator` property from json
if (-not $SkipMetadataCleanup) {
Write-Verbose "# Remove Bicep metadata from json - Processing [$($BicepFilesToConvert.count)] file(s)"
$removeScriptBlock = {
# helper function start
<#
.SYNOPSIS
Recursively remove 'metadata' `_generator` property from a provided object.
.DESCRIPTION
This object is expected to be an ARM template converted to a PowerShell custom object.
It uses the object reference rather than recreating/copying the object.
.PARAMETER TemplateObject
Mandatory. The ARM template converted to a PowerShell custom object.
.EXAMPLE
Remove-JSONMetadata -TemplateObject (ConvertFrom-Json (Get-Content -Path $JSONFilePath -Raw))
Reads content from a ARM/JSON file, converts it to a PSCustomObject and removes 'metadata' `_generator` property under the template and recursively on all nested deployments.
#>
function Remove-JSONMetadata {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[psobject] $TemplateObject
)
if (($TemplateObject | Get-Member -MemberType 'NoteProperty').Name -contains 'metadata' -and ($TemplateObject.metadata | Get-Member -MemberType 'NoteProperty').Name -contains '_generator') {
$TemplateObject.metadata.psobject.properties.Remove('_generator')
}
$TemplateObject.resources | Where-Object { $_.type -eq 'Microsoft.Resources/deployments' } | ForEach-Object {
Remove-JSONMetadata -TemplateObject $_.properties.template
}
}
# helper function end
$jsonFilePath = Join-Path (Split-Path $_ -Parent) ('{0}.json' -f (Split-Path $_ -LeafBase))
Write-Verbose (' Removing metadata `_generator` property from file [{0}]' -f $jsonFilePath) -Verbose
$JSONFileContent = Get-Content -Path $JSONFilePath
$JSONObj = $JSONFileContent | ConvertFrom-Json
Remove-JSONMetadata -TemplateObject $JSONObj
$JSONFileContent = $JSONObj | ConvertTo-Json -Depth 100
Set-Content -Value $JSONFileContent -Path $JSONFilePath
}
if ($PSCmdlet.ShouldProcess(('Metadata from [{0}] templates' -f ($BicepFilesToConvert.count)), 'Remove')) {
if ($RunSynchronous) {
$BicepFilesToConvert | ForEach-Object $removeScriptBlock
} else {
$BicepFilesToConvert | ForEach-Object -ThrottleLimit 4 -Parallel $removeScriptBlock
}
}
Write-Verbose ' Remove Bicep metadata from json - Done' -Verbose
}
#endregion
#region Remove bicep files and folders
if (-not $SkipBicepCleanUp) {
$dotBicepFoldersToRemove = Get-ChildItem -Path $Path -Filter '.bicep' -Recurse -Force -Directory
Write-Verbose "# Remove bicep files and folders - Remove [$($dotBicepFoldersToRemove.count)] .bicep folder(s)"
if ($PSCmdlet.ShouldProcess("[$($dotBicepFoldersToRemove.count)] .bicep folder(s) in path [$Path]", 'Remove-Item')) {
$dotBicepFoldersToRemove | Remove-Item -Recurse -Force
}
$BicepFilesToRemove = Get-ChildItem -Path $Path -Filter '*.bicep' -Recurse -Force -File
Write-Verbose "# Remove bicep files and folders - Remove [$($BicepFilesToRemove.count)] *.bicep file(s)"
if ($PSCmdlet.ShouldProcess("[$($BicepFilesToRemove.count)] *.bicep file(s) in path [$Path]", 'Remove-Item')) {
$BicepFilesToRemove | Remove-Item -Force
}
Write-Verbose 'Remove bicep files and folders - Done'
}
#endregion
#region Update pipeline files - Replace .bicep with .json in all workflow files
if (-not $SkipPipelineUpdate) {
# GitHub workflow files
$ghWorkflowFolderPath = Join-Path -Path $rootPath '.github' 'workflows'
if (Test-Path -Path $ghWorkflowFolderPath) {
$ghWorkflowFilesToUpdate = Get-ChildItem -Path $ghWorkflowFolderPath -Filter 'ms.*.yml' -File -Force
Write-Verbose ('# Update workflow files - Processing [{0}] file(s)' -f $ghWorkflowFilesToUpdate.count)
$ghWorkflowUpdateScriptBlock = {
$content = $_ | Get-Content
$content = $content -replace 'templateFilePath:(.*).bicep', 'templateFilePath:$1.json'
$_ | Set-Content -Value $content
}
if ($PSCmdlet.ShouldProcess(('[{0}] ms.*.yml file(s) in path [{1}]' -f $ghWorkflowFilesToUpdate.Count, $ghWorkflowFolderPath), 'Set-Content')) {
if ($RunSynchronous) {
$ghWorkflowFilesToUpdate | ForEach-Object $ghWorkflowUpdateScriptBlock
} else {
$ghWorkflowFilesToUpdate | ForEach-Object -ThrottleLimit 4 -Parallel $ghWorkflowUpdateScriptBlock
}
}
}
# Azure DevOps Pipelines
$adoPipelineFolderPath = Join-Path -Path $rootPath '.azuredevops' 'modulePipelines'
if (Test-Path -Path $adoPipelineFolderPath) {
$adoPipelineFilesToUpdate = Get-ChildItem -Path $adoPipelineFolderPath -Filter 'ms.*.yml' -File -Force
Write-Verbose ('Update Azure DevOps pipeline files - Processing [{0}] file(s)' -f $adoPipelineFilesToUpdate.count)
$adoPipelineUpdateScriptBlock = {
$content = $_ | Get-Content
$content = $content -replace 'templateFilePath:(.*).bicep', 'templateFilePath:$1.json'
$_ | Set-Content -Value $content
}
if ($PSCmdlet.ShouldProcess(('[{0}] ms.*.yml file(s) in path [{1}]' -f $adoPipelineFilesToUpdate.Count, $adoPipelineFolderPath), 'Set-Content')) {
if ($RunSynchronous) {
$adoPipelineFilesToUpdate | ForEach-Object $adoPipelineUpdateScriptBlock
} else {
$adoPipelineFilesToUpdate | ForEach-Object -ThrottleLimit 4 -Parallel $adoPipelineUpdateScriptBlock
}
}
}
Write-Verbose 'Update pipeline files - Done'
}
#endregion
}
#endregion