tasks/bicep.tasks.ps1

$BaseBranch = "origin/main"
$BicepRegistryFqdn = $env:BicepRegistryFqdn
$RegistryPath = $env:RegistryPath
$BicepTemplatesDir = $SourcesDir
$BicepModulesDir = ""
$OverwriteTag = $false
$AlwaysTag = $false
$RequiredBicepCliVersion = ""
$MinimumBicepCliVersion = "0.8.9"

function getAllBicepFiles {
    [CmdletBinding()]
    param (
        [Parameter()]
        [string] $Path = $BicepTemplatesDir
    )
    Write-Verbose "Looking for Bicep templates in '$Path'"
    Get-ChildItem -Recurse -Filter *.bicep -Path $Path
}

function getChangedBicepModules {
    $changedModules = git diff $BaseBranch --name-only |
        Where-Object { $_.StartsWith("modules") } | 
        ForEach-Object { $split = $_ -split "/"; $split[1..2] -join "/" } |
        Where-Object { $_.Contains("/") } |
        Where-Object { Test-path $BicepModulesDir/$_ } |
        Select-Object -Unique

    Write-Host "Changed modules:`n$($changedModules -join "`n")"

    $changedModules
}

# Synopsis: Lints Bicep files and builds the ARM template
task BuildBicepFiles `
    -Partial `
    -Inputs { getAllBicepFiles } `
    -Outputs {
        process { [System.IO.Path]::ChangeExtension($_, 'json')}
    } `
    -Jobs InstallBicepTooling,{
        begin {
            $failBuild = $false
        }
        process {
            Write-Build White "Building: $_"
            & az bicep build -f $_
            if ($LASTEXITCODE -ne 0) {
                $failBuild = $true
            }    
        }
        end {
            if ($failBuild) {
                throw "Bicep build error(s) - check preceeding log messages"
            }
            else {
                Write-Build Green "Bicep files OK"
            }    
        }
    }

# Synopsis: Installs Bicep CLI and Bicep Registry Module tooling
task InstallBicepTooling -If { getAllBicepFiles } DetectBuildServer,{
    Write-Build White "Checking Bicep CLI version:"
    $script:currentBicepVersion = $null
    $currentVersionBanner = try { & bicep --version } catch {}
    if ($currentVersionBanner) {
        Write-Build White $currentVersionBanner
        if ($currentVersionBanner -imatch "\d+(\.\d+)+") {
            $script:currentBicepVersion = [version]$Matches[0]
            Write-Build White $currentBicepVersion
        }
    }
    else {
        Write-Warning "Bicep tooling is not installed"
    }
    
    # Implement the comparison logic for the required & minimum version scenarios
    # A required version takes precedent over a minimum version
    if ($RequiredBicepCliVersion) {
        Write-Build White "Checking for exact Bicep version $RequiredBicepCliVersion"
        $currentVersionOk = $currentBicepVersion -eq [version]$RequiredBicepCliVersion
    }
    else {
        Write-Build White "Checking for minimum Bicep version $MinimumBicepCliVersion"
        $currentVersionOk = $currentBicepVersion -ge [version]$MinimumBicepCliVersion
    }

    # Only install Bicep CLI if it's not already installed or if it's an older version
    if (!$currentBicepVersion -or !$currentVersionOk ) {
        if ($IsRunningOnBuildServer) {
            $bitness = [System.Environment]::Is64BitOperatingSystem ? "x64" : "x86"
            if ($IsMacOS) {
                $downloadFile = "bicep-osx-$bitness"
            }
            elseif ($IsLinux) {
                $downloadFile = "bicep-linux-$bitness"
            }
            else {
                $downloadFile = "bicep-win-$bitness.exe"
            }

            # Setup temporary folder for Bicep CLI installation
            $destPath = $IsWindows ? "$($env:TEMP)/bicepcli/bicep" : "/tmp/bicepcli/bicep"
            New-Item -ItemType Directory (Split-Path -Path $destPath) -EA 0 | Out-Null
            # Determine which version to download based on whether an exact or minimum version has been specified
            $versionToDownload = $RequiredBicepCliVersion ? "v$RequiredBicepCliVersion" : "v$MinimumBicepCliVersion"
            $downloadUrl = "https://github.com/Azure/bicep/releases/download/$versionToDownload/$downloadFile"
            Write-Build White "Downloading Bicep CLI: $downloadUrl ==> $destPath"
            
            $res = Invoke-WebRequest `
                    -Uri $downloadUrl `
                    -OutFile $destPath

            if ($IsMacOS -or $IsLinux) {
                & chmod +x $destPath
            }

            Write-Build White "Updating PATH to use the newly-installed Bicep CLI"
            $env:PATH = "{0}{1}{2}" -f `
                            (Split-Path -Parent $destPath),
                            [IO.Path]::PathSeparator,
                            $env:PATH

            Get-Command bicep | Out-String | Write-Build White

            # Record the Bicep version that has been installed
            $script:currentBicepVersion = [version]$versionToDownload.TrimStart("v")
        }
        else {
            if ($RequiredBicepCliVersion) {
                throw ("Bicep tooling mismatch. Required version is '$RequiredBicepCliVersion', please update your installed version. [CurrentVersion=$currentBicepVersion]")
            }
            else {
                throw ("Bicep tooling mismatch. Minimum version is '$MinimumBicepCliVersion', please update your installed version. [CurrentVersion=$currentBicepVersion]")
            }
        }
    }

    # Ensure the Bicep Registry Module tool is installed with a version consistent with the installed version of the Bicep CLI
    Write-Build White "Ensuring matching version of 'brm' tool"
    Install-DotNetTool Azure.Bicep.RegistryModuleTool -Version $currentBicepVersion.ToString()
}

# Synopsis: Installs the Nerdbank GitVersion global tool
task InstallNbgvTool {
    Install-DotNetTool nbgv
}

# Synopsis: Validates all Bicep modules via 'brm validate'
task ValidateBicepModules -If { $BicepModulesDir } `
    -Inputs { getAllBicepFiles -Path $BicepModulesDir | ? { !$_.FullName.EndsWith(".test.bicep") } } `
    -Outputs "always-run" `
    -Jobs InstallBicepTooling,{
        begin {
            $passed = $true
        }
        process {
            Push-Location (Split-Path -Parent $_)
            Write-Build White "Validating $_"
            & brm validate
            if ($LASTEXITCODE -ne 0) {
                $passed = $false
            }
            Pop-Location 
        }
        end {
            if (!$passed) {
                throw "Validation failed for one more modules - check previous logs"
            }
            else {
                Write-Build Green "All Bicep modules validated successfully"
            }
        }
    }

# synopsis: Updates generated content for Bicep modules via 'brm generate'
task RunBrmGenerate  -If { $BicepModulesDir } `
    -Inputs { getAllBicepFiles -Path $BicepModulesDir | ? { !$_.FullName.EndsWith(".test.bicep") } } `
    -Outputs "always-run" `
    -Jobs InstallBicepTooling,{
        begin {
            $passed = $true
        }
        process {
            Push-Location (Split-Path -Parent $_)
            Write-Build White "Regenerating $_"
            & brm generate
            if ($LASTEXITCODE -ne 0) {
                $passed = $false
            }
            Pop-Location 
        }
        end {
            if (!$passed) {
                throw "Generation failed for one more modules - check previous logs"
            }
            else {
                Write-Build Green "All Bicep modules regenerated successfully"
            }
        }
    }
    
# Synopsis: Publishes any updated Bicep modules via 'bicep publish'
task PublishBicepModules -If { $BicepModulesDir } `
    -Inputs { getAllBicepFiles | ? { !$_.FullName.EndsWith(".test.bicep") } } `
    -Outputs "always-run" `
    -Jobs InstallBicepTooling,InstallNbgvTool, {
        # validate publish details
        if (!$BicepRegistryFqdn) {
            throw "The 'BicepRegistryFqdn' variables has not been defined"
        }
        elseif (!$BicepRegistryFqdn.EndsWith(".azurecr.io")) {
            throw "The 'BicepRegistryFqdn' must point to an Azure Container Registry with the '.azurecr.io' suffix - current value: $BicepRegistryFqdn"
        }

        # Publishing Bicep modules requires a logged-in Azure-Cli session
        if (!(Test-AzCliConnection)) {
            throw "You must be logged-in to azure-cli to publish Bicep modules to a private registry"
        }

        $modulesToPublish = getChangedBicepModules

        $gitTagsToPush = $false
        foreach ($module in $modulesToPublish) {
            Write-Build White "Processing module: $module"

            # Derive next version using nbgv
            Push-Location $BicepModulesDir/$module
            $res = & nbgv get-version --format json
            Pop-Location

            if ($res -eq $null) {
                Write-Warning "Error deriving version for '$module'"
                continue
            }
            $nbgvResults = $res | ConvertFrom-Json
            $semver = $nbgvResults.SemVer2
            [bool]$isPublicRelease = $nbgvResults.PublicRelease

            # Publish to ACR
            $moduleRegistryFullPath = "$BicepRegistryFqdn/$RegistryPath/$($module):$semver"
            bicep publish "$BicepModulesDir/$module/main.json" --target "br:$moduleRegistryFullPath"
            if ($LASTEXITCODE -ne 0) {
                Write-Warning "Error publishing module '$module' to '$moduleRegistryFullPath'"
                continue
            }
            Write-Build Green " Published - OK"

            # Create/push tag
            $tag = "$module/$semver"
            
            # Tag all 'public releases' as identified by nbgv or when explicitly requested
            if ($isPublicRelease -or $AlwaysTag) {
                # By default don't update existing git tags
                if ($OverwriteTag) {
                    Write-Build White " Any existing tag will be updated"
                    & git tag -f $tag
                }
                else {
                    & git tag $tag
                }
                if ($LASTEXITCODE -ne 0) {
                    Write-Warning "Error tagging module '$module' with '$tag'"
                    continue
                }
                $gitTagsToPush = $true
                Write-Build Green " Tagging - OK"
            }
            else {
                Write-Build White " Tagging - Skipped"
            }
            
        }

        if ($gitTagsToPush) {
            Write-Build White "Pushing module tags..."
            if ($OverwriteTag) {
                Write-Build White " Any existing tag will be updated"
                & git push --tags -f
            }
            else {
                & git push --tags
            }
            if ($LASTEXITCODE -ne 0) {
                Write-Warning "Error pushing modules' git tags to remote repo"
            }
            else {
                Write-Build Green "Pushed module tags - OK"
            }
        }
        else {
            Write-Build White "No module tags to push"
        }
    }

# Synopsis: Build for running locally that includes regenerating module content files
task LocalBicepBuild RunBrmGenerate,FullBuild