From 4ae6c8f4f1e927b0bf447c8c5a5955a99c2545cf Mon Sep 17 00:00:00 2001 From: Alexander Sehr Date: Sun, 3 Dec 2023 01:54:32 +0100 Subject: [PATCH] feat: Implemented post-deployment validation (#668) ## Description - Implemented post-deployment validation - Improved robustness of `diff` logic | Pipeline | | - | | https://github.com/AlexanderSehr/bicep-registry-modules/actions/runs/6982605270/job/19002095467#step:4:165 | ## Example Output ``` VERBOSE: Invoke function with VERBOSE: { "Version": "0.3", "ModulePath": "avm/res/key-vault/vault" } VERBOSE: Requested HTTP/1.1 GET with 0-byte payload VERBOSE: Received HTTP/1.1 71191-byte response of content type application/json VERBOSE: Bicep modules found in MCR catalog: bicep/samples/hello-world bicep/samples/array-loop bicep/ai/bing-resource bicep/ai/cognitiveservices bicep/app/app-configuration bicep/app/dapr-containerapp bicep/app/dapr-containerapps-environment bicep/authorization/resource-scope-role-assignment bicep/azure-gaming/game-dev-vm bicep/azure-gaming/game-dev-vmss bicep/compute/availability-set bicep/compute/container-registry bicep/compute/custom-image-vmss bicep/compute/event-hub bicep/compute/function-app bicep/cost/resourcegroup-scheduled-action bicep/cost/subscription-scheduled-action bicep/deployment-scripts/aks-run-command bicep/deployment-scripts/aks-run-helm bicep/deployment-scripts/build-acr bicep/deployment-scripts/create-kv-certificate bicep/deployment-scripts/create-kv-sshkeypair bicep/deployment-scripts/import-acr bicep/deployment-scripts/wait bicep/lz/sub-vending bicep/identity/user-assigned-identity bicep/network/virtual-network bicep/network/traffic-manager bicep/network/nat-gateway bicep/network/dns-zone bicep/network/public-ip-address bicep/network/public-ip-prefix bicep/network/private-dns-zone bicep/observability/grafana bicep/search/search-service bicep/security/keyvault bicep/storage/cosmos-db bicep/storage/data-explorer bicep/storage/storage-account bicep/storage/log-analytics-workspace bicep/storage/postgresql-single-server bicep/storage/redis-cache bicep/storage/mysql-single-server bicep/avm/res/batch/batch-account bicep/avm/res/cognitive-services/account bicep/avm/res/compute/ssh-public-key bicep/avm/res/event-grid/domain bicep/avm/res/event-grid/system-topic bicep/avm/res/insights/action-group bicep/avm/res/insights/component bicep/avm/res/insights/diagnostic-setting bicep/avm/res/key-vault/vault bicep/avm/res/kubernetes-configuration/extension bicep/avm/res/kubernetes-configuration/flux-configuration bicep/avm/res/logic/workflow bicep/avm/res/network/dns-forwarding-ruleset bicep/avm/res/network/dns-resolver bicep/avm/res/network/dns-zone bicep/avm/res/network/express-route-circuit bicep/avm/res/network/express-route-gateway bicep/avm/res/network/load-balancer bicep/avm/res/network/network-interface bicep/avm/res/network/private-dns-zone bicep/avm/res/network/private-endpoint bicep/avm/res/network/public-ip-address bicep/avm/res/operational-insights/workspace bicep/avm/res/operations-management/solution bicep/avm/res/power-bi-dedicated/capacity bicep/avm/res/resource-graph/query bicep/avm/res/search/search-service bicep/avm/res/sql/server VERBOSE: Passed: Found module [avm/res/key-vault/vault] in the MCR catalog VERBOSE: Requested HTTP/1.1 GET with 0-byte payload VERBOSE: Received HTTP/1.1 87-byte response of content type application/json VERBOSE: Tags for module in path [avm/res/key-vault/vault] found in MCR catalog: 0.2.0 0.3.0 Passed: Found new tag [0.3] for published module ``` --------- Co-authored-by: Erika Gressi <56914614+eriqua@users.noreply.github.com> Co-authored-by: ChrisSidebotham-MSFT <48600046+ChrisSidebotham@users.noreply.github.com> --- .../templates/avm-publishModule/action.yml | 30 ++++++- .../publish/Confirm-ModuleIsPublished.ps1 | 81 +++++++++++++++++++ .../publish/Publish-ModuleFromPathToPBR.ps1 | 5 ++ .../publish/helper/Get-ModulesToPublish.ps1 | 2 +- 4 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 avm/utilities/pipelines/publish/Confirm-ModuleIsPublished.ps1 diff --git a/.github/actions/templates/avm-publishModule/action.yml b/.github/actions/templates/avm-publishModule/action.yml index 2aa36f1dcf..a5c1ba60ac 100644 --- a/.github/actions/templates/avm-publishModule/action.yml +++ b/.github/actions/templates/avm-publishModule/action.yml @@ -46,6 +46,7 @@ runs: bicep --version - name: "Publish module to public bicep registry" + id: publish_step uses: azure/powershell@v1 with: azPSVersion: "latest" @@ -68,8 +69,33 @@ runs: Write-Verbose ($functionInput | ConvertTo-Json | Out-String) -Verbose # Get the modified child resources - Publish-ModuleFromPathToPBR @functionInput -Verbose + if($publishOutputs = Publish-ModuleFromPathToPBR @functionInput -Verbose) { + Write-Output ('{0}={1}' -f 'version', $publishOutputs.version) >> $env:GITHUB_OUTPUT + Write-Output ('{0}={1}' -f 'publishedModuleName', $publishOutputs.publishedModuleName) >> $env:GITHUB_OUTPUT + } Write-Output '::endgroup::' - # TODO Add publish validation (as per PBR pipeline template 'publish-module.yml') + - name: "Validate publish" + uses: azure/powershell@v1 + if: ${{ steps.publish_step.outputs.version != '' && steps.publish_step.outputs.publishedModuleName != '' }} + with: + azPSVersion: "latest" + inlineScript: | + # Grouping task logs + Write-Output '::group::Validate publish' + + # Load used functions + . (Join-Path $env:GITHUB_WORKSPACE 'avm' 'utilities' 'pipelines' 'publish' 'Confirm-ModuleIsPublished.ps1') + + $functionInput = @{ + Version = "${{ steps.publish_step.outputs.version }}" + PublishedModuleName = "${{ steps.publish_step.outputs.publishedModuleName }}" + } + + Write-Verbose "Invoke function with" -Verbose + Write-Verbose ($functionInput | ConvertTo-Json | Out-String) -Verbose + + Confirm-ModuleIsPublished @functionInput -Verbose + + Write-Output '::endgroup::' diff --git a/avm/utilities/pipelines/publish/Confirm-ModuleIsPublished.ps1 b/avm/utilities/pipelines/publish/Confirm-ModuleIsPublished.ps1 new file mode 100644 index 0000000000..472600ae7b --- /dev/null +++ b/avm/utilities/pipelines/publish/Confirm-ModuleIsPublished.ps1 @@ -0,0 +1,81 @@ +<# +.SYNOPSIS +Check if a module in a given path is published in a given version + +.DESCRIPTION +Check if a module in a given path is published in a given version. Tries to find the module & version for a maximum of 60 minutes. + +.PARAMETER Version +Mandatory. The version of the module to check for. For example: '0.2.0' + +.PARAMETER PublishedModuleName +Mandatory. The path of the module to check for. For example: 'avm/res/key-vault/vault' + +.EXAMPLE +Confirm-ModuleIsPublished -Version '0.2.0' -PublishedModuleName 'avm/res/key-vault/vault' -Verbose + +Check if module 'key-vault/vault' has been published with version '0.2.0 +#> +function Confirm-ModuleIsPublished { + + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [string] $Version, + + [Parameter(Mandatory)] + [string] $PublishedModuleName + ) + + $baseUrl = 'https://mcr.microsoft.com/v2' + $catalogUrl = "$baseUrl/_catalog" + $moduleVersionsUrl = "$baseUrl/bicep/$PublishedModuleName/tags/list" + + $time_limit_seconds = 3600 # 1h + $end_time = (Get-Date).AddSeconds($time_limit_seconds) + $retry_seconds = 5 + + ##################################### + ## Confirm module is published ## + ##################################### + while ($true) { + $catalogContentRaw = (Invoke-WebRequest -Uri $catalogUrl -UseBasicParsing).Content + $bicepCatalogContent = ($catalogContentRaw | ConvertFrom-Json).repositories | Select-String 'bicep/' + Write-Verbose ("Bicep modules found in MCR catalog:`n{0}" -f ($bicepCatalogContent | Out-String)) + + if ($bicepCatalogContent -match "bicep/$PublishedModuleName") { + Write-Verbose "Passed: Found module [$PublishedModuleName] in the MCR catalog" -Verbose + break + } else { + Write-Error "Error: Module [$PublishedModuleName] is not in the MCR catalog. Retrying in [$retry_seconds] seconds" + Start-Sleep -Seconds $retry_seconds + } + + if ((Get-Date) -ge $end_time) { + throw "Time limit reached. Failed to validate publish of module in path [$PublishedModuleName] within the specified time." + } + } + + ############################################# + ## Confirm module version is published ## + ############################################# + while ($true) { + $tagsContentRaw = (Invoke-WebRequest -Uri $moduleVersionsUrl -UseBasicParsing).Content + $tagsContent = ($tagsContentRaw | ConvertFrom-Json).tags + + Write-Verbose ("Tags for module in path [$PublishedModuleName] found in MCR catalog:`n{0}" -f ($tagsContent | Out-String)) + + if ($tagsContent -match $Version) { + Write-Host "Passed: Found new tag [$Version] for published module" + break + } else { + Write-Host "Error: Could not find new tag [$Version] for published module. Retrying in [$retry_seconds] seconds" + Start-Sleep -Seconds $retry_seconds + } + + if ((Get-Date) -ge $end_time) { + Write-Host 'Time limit reached. Failed to validate publish within the specified time.' + exit 1 + } + } +} diff --git a/avm/utilities/pipelines/publish/Publish-ModuleFromPathToPBR.ps1 b/avm/utilities/pipelines/publish/Publish-ModuleFromPathToPBR.ps1 index 84d0029db6..1cbbbf6d22 100644 --- a/avm/utilities/pipelines/publish/Publish-ModuleFromPathToPBR.ps1 +++ b/avm/utilities/pipelines/publish/Publish-ModuleFromPathToPBR.ps1 @@ -96,4 +96,9 @@ function Publish-ModuleFromPathToPBR { Write-Verbose "Publish Input:`n $($publishInput | ConvertTo-Json -Depth 10)" -Verbose bicep publish @publishInput + + return @{ + version = $targetVersion + publishedModuleName = $publishedModuleName + } } diff --git a/avm/utilities/pipelines/publish/helper/Get-ModulesToPublish.ps1 b/avm/utilities/pipelines/publish/helper/Get-ModulesToPublish.ps1 index 4c503b3809..14e1a1401e 100644 --- a/avm/utilities/pipelines/publish/helper/Get-ModulesToPublish.ps1 +++ b/avm/utilities/pipelines/publish/helper/Get-ModulesToPublish.ps1 @@ -21,7 +21,7 @@ function Get-ModifiedFileList { Write-Verbose 'Gathering modified files from the previous head' -Verbose $Diff = git diff --name-only --diff-filter=AM HEAD^ HEAD } - $ModifiedFiles = $Diff | Get-Item -Force + $ModifiedFiles = $Diff ? ($Diff | Get-Item -Force) : @() return $ModifiedFiles }