From 37d404e1676e66c19b9f35bb1ce3797350a0a098 Mon Sep 17 00:00:00 2001 From: Neil Bird Date: Mon, 15 Jun 2026 16:14:15 +0100 Subject: [PATCH] AzLocal.UpdateManagement v0.8.83 (#NN) Fix-forward for v0.8.82 Item-5: the Step.08 `UpdateLastAttempt` reconciliation in `Export-AzLocalUpdateRunMonitorReport` reads `$inv.tags` to surface "Recent update attempts with no observable updateRun". v0.8.82 silently lost the raw ARM `tags` bag in `Get-AzLocalClusterInventory` (only the projected/flattened columns were retained on the PassThru objects), so the section was always empty in production. Changes: - `Get-AzLocalClusterInventory.ps1`: copy the raw `properties.tags` bag onto each PassThru row before the `$selectColumns` whitelist is applied. The whitelist still excludes `tags` from CSV/JSON exports, so on-disk artefacts are byte-identical. - `Export-AzLocalUpdateRunMonitorReport.ps1`: docstring updated from "6 step outputs" to "7 step outputs", listing the `attempts_without_run` output that Step.08 already emits via `Set-AzLocalPipelineOutput`. - `Automation-Pipeline-Examples/github-actions/monitor-updates.yml`: wire `attempts_without_run` into the workflow's `jobs.outputs:` block so downstream jobs can fan out on it. ADO equivalent already auto-publishes via `Set-AzLocalPipelineOutput`. - `Tests/AzLocal.UpdateManagement.Tests.ps1`: add 2 regression tests under "v0.8.83: Get-AzLocalClusterInventory preserves raw ARM tag bag on PassThru". - All 22 pipeline YAMLs: bump `GENERATED_AGAINST_MODULE_VERSION` pin to `'0.8.83'`. - Version constants: `ModuleVersion='0.8.83'` in psd1 + `$script:ModuleVersion='0.8.83'` in psm1. ReleaseNotes prepended (length 9936/10000). - CHANGELOG, release-history, README: standard release-doc updates. Test results: 1244 passed, 0 failed, 1 skipped (148.4s). --- .../apply-updates-schedule-audit.yml | 2 +- .../azure-devops/apply-updates.yml | 2 +- .../azure-devops/assess-update-readiness.yml | 2 +- .../azure-devops/authentication-test.yml | 2 +- .../fleet-connectivity-status.yml | 2 +- .../azure-devops/fleet-health-status.yml | 2 +- .../azure-devops/fleet-update-status.yml | 2 +- .../azure-devops/inventory-clusters.yml | 2 +- .../azure-devops/manage-updatering-tags.yml | 2 +- .../azure-devops/monitor-updates.yml | 7 +- .../azure-devops/sideload-updates.yml | 2 +- .../apply-updates-schedule-audit.yml | 2 +- .../github-actions/apply-updates.yml | 2 +- .../assess-update-readiness.yml | 2 +- .../github-actions/authentication-test.yml | 2 +- .../fleet-connectivity-status.yml | 2 +- .../github-actions/fleet-health-status.yml | 2 +- .../github-actions/fleet-update-status.yml | 2 +- .../github-actions/inventory-clusters.yml | 2 +- .../github-actions/manage-updatering-tags.yml | 2 +- .../github-actions/monitor-updates.yml | 8 +- .../github-actions/sideload-updates.yml | 2 +- .../AzLocal.UpdateManagement.psd1 | 6 +- .../AzLocal.UpdateManagement.psm1 | 2 +- AzLocal.UpdateManagement/CHANGELOG.md | 48 ++++++++++++ .../Export-AzLocalUpdateRunMonitorReport.ps1 | 4 +- .../Public/Get-AzLocalClusterInventory.ps1 | 2 + AzLocal.UpdateManagement/README.md | 39 +++++----- .../Tests/AzLocal.UpdateManagement.Tests.ps1 | 74 ++++++++++++++++++- .../docs/release-history.md | 10 ++- 30 files changed, 186 insertions(+), 54 deletions(-) diff --git a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/apply-updates-schedule-audit.yml b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/apply-updates-schedule-audit.yml index df65b935..9b2f479b 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/apply-updates-schedule-audit.yml +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/apply-updates-schedule-audit.yml @@ -87,7 +87,7 @@ parameters: default: false variables: - GENERATED_AGAINST_MODULE_VERSION: '0.8.82' + GENERATED_AGAINST_MODULE_VERSION: '0.8.83' REQUIRED_MODULE_VERSION: '${{ parameters.moduleVersion }}' reportsPath: '$(Build.ArtifactStagingDirectory)/reports' # v0.8.7 sideload advisor defaults. Override at the pipeline / variable-group diff --git a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/apply-updates.yml b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/apply-updates.yml index 2f7e4767..6f1c597d 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/apply-updates.yml +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/apply-updates.yml @@ -129,7 +129,7 @@ variables: # log if the YAML appears stale - prompting you to refresh via # Copy-AzLocalPipelineExample -Update. See Automation-Pipeline-Examples/README.md section 5. - name: GENERATED_AGAINST_MODULE_VERSION - value: '0.8.82' + value: '0.8.83' # Resolution order for the module version pin (leave all unset to install the latest, # which is the default "fix-forward" behaviour): queue-time parameter > pipeline variable # 'REQUIRED_MODULE_VERSION' overridden at queue time > empty (latest). diff --git a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/assess-update-readiness.yml b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/assess-update-readiness.yml index ba872542..e07ec3d9 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/assess-update-readiness.yml +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/assess-update-readiness.yml @@ -70,7 +70,7 @@ variables: # the version actually installed and to the latest on PSGallery, and emits a warning # log if the YAML appears stale - prompting you to refresh via # Copy-AzLocalPipelineExample -Update. See Automation-Pipeline-Examples/README.md section 5. - GENERATED_AGAINST_MODULE_VERSION: '0.8.82' + GENERATED_AGAINST_MODULE_VERSION: '0.8.83' # Resolution order for the module version pin (leave all unset to install the latest, # which is the default "fix-forward" behaviour): queue-time parameter > pipeline variable # 'REQUIRED_MODULE_VERSION' overridden at queue time > empty (latest). diff --git a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/authentication-test.yml b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/authentication-test.yml index f1d3f49f..e82b660f 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/authentication-test.yml +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/authentication-test.yml @@ -60,7 +60,7 @@ variables: # log if the YAML appears stale - prompting you to refresh via # Copy-AzLocalPipelineExample -Update. See Automation-Pipeline-Examples/README.md section 5. - name: GENERATED_AGAINST_MODULE_VERSION - value: '0.8.82' + value: '0.8.83' # Resolution order for the module version pin (leave all unset to install the latest, # which is the default "fix-forward" behaviour): queue-time parameter > pipeline variable # 'REQUIRED_MODULE_VERSION' overridden at queue time > empty (latest). diff --git a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/fleet-connectivity-status.yml b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/fleet-connectivity-status.yml index e704ec57..f1091a90 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/fleet-connectivity-status.yml +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/fleet-connectivity-status.yml @@ -110,7 +110,7 @@ variables: # the version actually installed and to the latest on PSGallery, and emits a warning # log if the YAML appears stale - prompting you to refresh via # Copy-AzLocalPipelineExample -Update. See Automation-Pipeline-Examples/README.md section 5. - GENERATED_AGAINST_MODULE_VERSION: '0.8.82' + GENERATED_AGAINST_MODULE_VERSION: '0.8.83' # Resolution order for the module version pin (leave all unset to install the latest, # which is the default "fix-forward" behaviour): queue-time parameter > pipeline variable # 'REQUIRED_MODULE_VERSION' overridden at queue time > empty (latest). diff --git a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/fleet-health-status.yml b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/fleet-health-status.yml index 18d3d941..fcf13284 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/fleet-health-status.yml +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/fleet-health-status.yml @@ -105,7 +105,7 @@ variables: # the version actually installed and to the latest on PSGallery, and emits a warning # log if the YAML appears stale - prompting you to refresh via # Copy-AzLocalPipelineExample -Update. See Automation-Pipeline-Examples/README.md section 5. - GENERATED_AGAINST_MODULE_VERSION: '0.8.82' + GENERATED_AGAINST_MODULE_VERSION: '0.8.83' # Resolution order for the module version pin (leave all unset to install the latest, # which is the default "fix-forward" behaviour): queue-time parameter > pipeline variable # 'REQUIRED_MODULE_VERSION' overridden at queue time > empty (latest). diff --git a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/fleet-update-status.yml b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/fleet-update-status.yml index 56eecbbf..f2e248fd 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/fleet-update-status.yml +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/fleet-update-status.yml @@ -93,7 +93,7 @@ variables: # the version actually installed and to the latest on PSGallery, and emits a warning # log if the YAML appears stale - prompting you to refresh via # Copy-AzLocalPipelineExample -Update. See Automation-Pipeline-Examples/README.md section 5. - GENERATED_AGAINST_MODULE_VERSION: '0.8.82' + GENERATED_AGAINST_MODULE_VERSION: '0.8.83' # Resolution order for the module version pin (leave all unset to install the latest, # which is the default "fix-forward" behaviour): queue-time parameter > pipeline variable # 'REQUIRED_MODULE_VERSION' overridden at queue time > empty (latest). diff --git a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/inventory-clusters.yml b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/inventory-clusters.yml index f6a14518..a0ba88ff 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/inventory-clusters.yml +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/inventory-clusters.yml @@ -43,7 +43,7 @@ variables: # log if the YAML appears stale - prompting you to refresh via # Copy-AzLocalPipelineExample -Update. See Automation-Pipeline-Examples/README.md section 5. - name: GENERATED_AGAINST_MODULE_VERSION - value: '0.8.82' + value: '0.8.83' # Resolution order for the module version pin (leave all unset to install the latest, # which is the default "fix-forward" behaviour): queue-time parameter > pipeline variable # 'REQUIRED_MODULE_VERSION' overridden at queue time > empty (latest). diff --git a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/manage-updatering-tags.yml b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/manage-updatering-tags.yml index 36c6cf6a..74ddd2fe 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/manage-updatering-tags.yml +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/manage-updatering-tags.yml @@ -46,7 +46,7 @@ variables: # log if the YAML appears stale - prompting you to refresh via # Copy-AzLocalPipelineExample -Update. See Automation-Pipeline-Examples/README.md section 5. - name: GENERATED_AGAINST_MODULE_VERSION - value: '0.8.82' + value: '0.8.83' # Resolution order for the module version pin (leave all unset to install the latest, # which is the default "fix-forward" behaviour): queue-time parameter > pipeline variable # 'REQUIRED_MODULE_VERSION' overridden at queue time > empty (latest). diff --git a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/monitor-updates.yml b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/monitor-updates.yml index 1afbd133..f0944baa 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/monitor-updates.yml +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/monitor-updates.yml @@ -84,7 +84,7 @@ parameters: default: '' variables: - GENERATED_AGAINST_MODULE_VERSION: '0.8.82' + GENERATED_AGAINST_MODULE_VERSION: '0.8.83' REQUIRED_MODULE_VERSION: '${{ parameters.moduleVersion }}' reportsPath: '$(Build.ArtifactStagingDirectory)/reports' @@ -137,9 +137,10 @@ stages: # failed tables) has been condensed into the Public cmdlet # Export-AzLocalUpdateRunMonitorReport. The cmdlet writes # $(reportsPath)/update-monitor.csv + .xml, uploads the markdown - # summary via ##vso[task.uploadsummary], and sets the six pipeline + # summary via ##vso[task.uploadsummary], and sets the seven pipeline # variables (in_flight, long_running, long_running_step, - # step_errored, recent_failures, unresolved_failures). + # step_errored, recent_failures, unresolved_failures, + # attempts_without_run). inputs: azureSubscription: 'AzureLocal-ServiceConnection' # Update with your service connection name scriptType: 'pscore' diff --git a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/sideload-updates.yml b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/sideload-updates.yml index 50757f39..4467f025 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/sideload-updates.yml +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/sideload-updates.yml @@ -79,7 +79,7 @@ variables: # the version actually installed and to the latest on PSGallery, and emits a warning # log if the YAML appears stale - prompting you to refresh via # Copy-AzLocalPipelineExample -Update. See Automation-Pipeline-Examples/README.md section 5. - GENERATED_AGAINST_MODULE_VERSION: '0.8.82' + GENERATED_AGAINST_MODULE_VERSION: '0.8.83' # Resolution order for the module version pin (leave all unset to install the latest, # which is the default "fix-forward" behaviour): queue-time parameter > pipeline variable # 'REQUIRED_MODULE_VERSION' overridden at queue time > empty (latest). diff --git a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/apply-updates-schedule-audit.yml b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/apply-updates-schedule-audit.yml index 0a4dec12..627f1c30 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/apply-updates-schedule-audit.yml +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/apply-updates-schedule-audit.yml @@ -96,7 +96,7 @@ env: # this to the version actually installed and to the latest on PSGallery, and emits a # ::notice annotation if the YAML appears stale - prompting you to refresh via # Copy-AzLocalPipelineExample -Update. See Automation-Pipeline-Examples/README.md section 5. - GENERATED_AGAINST_MODULE_VERSION: '0.8.82' + GENERATED_AGAINST_MODULE_VERSION: '0.8.83' REQUIRED_MODULE_VERSION: ${{ github.event.inputs.module_version || vars.REQUIRED_MODULE_VERSION || '' }} # v0.8.4 - opt this workflow into Node.js 24 for all JavaScript actions # (actions/checkout, actions/download-artifact, actions/upload-artifact, diff --git a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/apply-updates.yml b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/apply-updates.yml index 70ee333a..26e0e637 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/apply-updates.yml +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/apply-updates.yml @@ -148,7 +148,7 @@ env: # this to the version actually installed and to the latest on PSGallery, and emits a # ::notice annotation if the YAML appears stale - prompting you to refresh via # Copy-AzLocalPipelineExample -Update. See Automation-Pipeline-Examples/README.md section 5. - GENERATED_AGAINST_MODULE_VERSION: '0.8.82' + GENERATED_AGAINST_MODULE_VERSION: '0.8.83' # Resolution order for the module version pin (leave all unset to install the latest, # which is the default "fix-forward" behaviour): manual workflow_dispatch input > # repository variable 'REQUIRED_MODULE_VERSION' > empty (latest). diff --git a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/assess-update-readiness.yml b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/assess-update-readiness.yml index e4c5c94e..5e5854ee 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/assess-update-readiness.yml +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/assess-update-readiness.yml @@ -72,7 +72,7 @@ env: # this to the version actually installed and to the latest on PSGallery, and emits a # ::notice annotation if the YAML appears stale - prompting you to refresh via # Copy-AzLocalPipelineExample -Update. See Automation-Pipeline-Examples/README.md section 5. - GENERATED_AGAINST_MODULE_VERSION: '0.8.82' + GENERATED_AGAINST_MODULE_VERSION: '0.8.83' # Resolution order for the module version pin (leave all unset to install the latest, # which is the default "fix-forward" behaviour): manual workflow_dispatch input > # repository variable 'REQUIRED_MODULE_VERSION' > empty (latest). diff --git a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/authentication-test.yml b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/authentication-test.yml index 03d5f4d9..7337e221 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/authentication-test.yml +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/authentication-test.yml @@ -79,7 +79,7 @@ env: # this to the version actually installed and to the latest on PSGallery, and emits a # ::notice annotation if the YAML appears stale - prompting you to refresh via # Copy-AzLocalPipelineExample. See Automation-Pipeline-Examples/README.md section 5. - GENERATED_AGAINST_MODULE_VERSION: '0.8.82' + GENERATED_AGAINST_MODULE_VERSION: '0.8.83' # Resolution order for the module version pin (leave all unset to install the latest, # which is the default "fix-forward" behaviour): manual workflow_dispatch input > # repository variable 'REQUIRED_MODULE_VERSION' > empty (latest). diff --git a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/fleet-connectivity-status.yml b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/fleet-connectivity-status.yml index 3cbfae1a..9bd1e317 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/fleet-connectivity-status.yml +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/fleet-connectivity-status.yml @@ -116,7 +116,7 @@ env: # PSGallery, and emits a ::notice annotation if the YAML appears stale - # prompting you to refresh via Copy-AzLocalPipelineExample -Update. See # Automation-Pipeline-Examples/README.md section 5. - GENERATED_AGAINST_MODULE_VERSION: '0.8.82' + GENERATED_AGAINST_MODULE_VERSION: '0.8.83' # Resolution order for the module version pin (leave all unset to install # the latest, the default "fix-forward" behaviour): manual workflow_dispatch # input > repository variable 'REQUIRED_MODULE_VERSION' > empty (latest). diff --git a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/fleet-health-status.yml b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/fleet-health-status.yml index 0603bbb0..7b66bd9f 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/fleet-health-status.yml +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/fleet-health-status.yml @@ -116,7 +116,7 @@ env: # this to the version actually installed and to the latest on PSGallery, and emits a # ::notice annotation if the YAML appears stale - prompting you to refresh via # Copy-AzLocalPipelineExample -Update. See Automation-Pipeline-Examples/README.md section 5. - GENERATED_AGAINST_MODULE_VERSION: '0.8.82' + GENERATED_AGAINST_MODULE_VERSION: '0.8.83' # Resolution order for the module version pin (leave all unset to install the latest, # which is the default "fix-forward" behaviour): manual workflow_dispatch input > # repository variable 'REQUIRED_MODULE_VERSION' > empty (latest). diff --git a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/fleet-update-status.yml b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/fleet-update-status.yml index 461004f2..5e822334 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/fleet-update-status.yml +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/fleet-update-status.yml @@ -105,7 +105,7 @@ env: # this to the version actually installed and to the latest on PSGallery, and emits a # ::notice annotation if the YAML appears stale - prompting you to refresh via # Copy-AzLocalPipelineExample -Update. See Automation-Pipeline-Examples/README.md section 5. - GENERATED_AGAINST_MODULE_VERSION: '0.8.82' + GENERATED_AGAINST_MODULE_VERSION: '0.8.83' # Resolution order for the module version pin (leave all unset to install the latest, # which is the default "fix-forward" behaviour): manual workflow_dispatch input > # repository variable 'REQUIRED_MODULE_VERSION' > empty (latest). diff --git a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/inventory-clusters.yml b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/inventory-clusters.yml index a3d472dd..e06a1bf2 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/inventory-clusters.yml +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/inventory-clusters.yml @@ -48,7 +48,7 @@ env: # this to the version actually installed and to the latest on PSGallery, and emits a # ::notice annotation if the YAML appears stale - prompting you to refresh via # Copy-AzLocalPipelineExample. See Automation-Pipeline-Examples/README.md section 5. - GENERATED_AGAINST_MODULE_VERSION: '0.8.82' + GENERATED_AGAINST_MODULE_VERSION: '0.8.83' # Resolution order for the module version pin (leave all unset to install the latest, # which is the default "fix-forward" behaviour): manual workflow_dispatch input > # repository variable 'REQUIRED_MODULE_VERSION' > empty (latest). diff --git a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/manage-updatering-tags.yml b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/manage-updatering-tags.yml index 381cb051..b74df924 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/manage-updatering-tags.yml +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/manage-updatering-tags.yml @@ -65,7 +65,7 @@ env: # this to the version actually installed and to the latest on PSGallery, and emits a # ::notice annotation if the YAML appears stale - prompting you to refresh via # Copy-AzLocalPipelineExample -Update. See Automation-Pipeline-Examples/README.md section 5. - GENERATED_AGAINST_MODULE_VERSION: '0.8.82' + GENERATED_AGAINST_MODULE_VERSION: '0.8.83' # Resolution order for the module version pin (leave all unset to install the latest, # which is the default "fix-forward" behaviour): manual workflow_dispatch input > # repository variable 'REQUIRED_MODULE_VERSION' > empty (latest). diff --git a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/monitor-updates.yml b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/monitor-updates.yml index f6ebbfee..aff8f36c 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/monitor-updates.yml +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/monitor-updates.yml @@ -86,7 +86,7 @@ env: # this to the version actually installed and to the latest on PSGallery, and emits a # ::notice annotation if the YAML appears stale - prompting you to refresh via # Copy-AzLocalPipelineExample -Update. See Automation-Pipeline-Examples/README.md section 5. - GENERATED_AGAINST_MODULE_VERSION: '0.8.82' + GENERATED_AGAINST_MODULE_VERSION: '0.8.83' # Resolution order for the module version pin (leave all unset to install the latest, # which is the default "fix-forward" behaviour): manual workflow_dispatch input > # repository variable 'REQUIRED_MODULE_VERSION' > empty (latest). @@ -121,6 +121,12 @@ jobs: step_errored: ${{ steps.snapshot.outputs.step_errored }} recent_failures: ${{ steps.snapshot.outputs.recent_failures }} unresolved_failures: ${{ steps.snapshot.outputs.unresolved_failures }} + # v0.8.83: expose the Step.08 UpdateLastAttempt-reconciliation count + # (clusters whose UpdateLastAttempt tag points at an attempt with no + # observable updateRun resource - typically a URP package-internal + # pre-install health-check failure). Wired so downstream pipeline + # jobs can gate on attempts_without_run > 0. + attempts_without_run: ${{ steps.snapshot.outputs.attempts_without_run }} steps: - name: Checkout repository diff --git a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/sideload-updates.yml b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/sideload-updates.yml index 238cae2a..3ffc7cf6 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/sideload-updates.yml +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/sideload-updates.yml @@ -83,7 +83,7 @@ env: # this to the version actually installed and to the latest on PSGallery, and emits a # ::notice annotation if the YAML appears stale - prompting you to refresh via # Copy-AzLocalPipelineExample -Update. See Automation-Pipeline-Examples/README.md section 5. - GENERATED_AGAINST_MODULE_VERSION: '0.8.82' + GENERATED_AGAINST_MODULE_VERSION: '0.8.83' # Resolution order for the module version pin (leave all unset to install the latest, # which is the default "fix-forward" behaviour): manual workflow_dispatch input > # repository variable 'REQUIRED_MODULE_VERSION' > empty (latest). diff --git a/AzLocal.UpdateManagement/AzLocal.UpdateManagement.psd1 b/AzLocal.UpdateManagement/AzLocal.UpdateManagement.psd1 index 32f05894..3b8dd563 100644 --- a/AzLocal.UpdateManagement/AzLocal.UpdateManagement.psd1 +++ b/AzLocal.UpdateManagement/AzLocal.UpdateManagement.psd1 @@ -3,7 +3,7 @@ RootModule = 'AzLocal.UpdateManagement.psm1' # Version number of this module. - ModuleVersion = '0.8.82' + ModuleVersion = '0.8.83' # Supported PSEditions CompatiblePSEditions = @('Desktop', 'Core') @@ -317,6 +317,8 @@ # ReleaseNotes of this module ReleaseNotes = @' +## Version 0.8.83 - Patch: fix-forward for v0.8.82 Item-5. The Step.08 `UpdateLastAttempt` reconciliation in `Export-AzLocalUpdateRunMonitorReport` reads `$inv.tags` from `Get-AzLocalClusterInventory`, but the v0.8.82 inventory projection did not carry the raw ARM `tags` bag - so the "Recent update attempts with no observable updateRun" section was silently always empty in production. v0.8.83 surfaces the raw `tags` bag on every inventory row (in-memory only; the CSV / JSON export keeps its explicit `$selectColumns` whitelist - new regression test asserts both). Also wires `attempts_without_run` into the GitHub Actions `monitor-updates.yml` `jobs.outputs:` block (ADO `Set-AzLocalPipelineOutput` auto-publishes so only an ADO docstring refresh needed), and corrects the `Export-AzLocalUpdateRunMonitorReport` "6 step outputs" docstring to "7 step outputs". No public API change. Export count unchanged (still 60). All bundled pipeline templates bump `GENERATED_AGAINST_MODULE_VERSION` from `'0.8.82'` to `'0.8.83'`. + ## Version 0.8.82 - Patch: four Step-summary UX polish fixes from v0.8.81 manual pipeline-run review. (1) Step.05 Summary counts table no longer duplicates labels: each row reused the shared `Get-AzLocalStatusIconMap` cell (which already includes its own label) AND appended a duplicate trailing label, producing `Ready for Update Ready for update` / `Up to Date Up to date` / `Action Required Not ready for update` / `Health Failure Clusters with Critical health failures`. Fixed by emitting the icon-map cell unmodified; the HealthFailure row keeps `(Clusters with Critical health failures)` in parentheses since it counts something different from the readiness cascade. (2) Step.05 All clusters detail table now sorts by Status priority first (`InProgress` -> `HealthFailure` -> `UpdateFailed` -> `ActionRequired` -> `SbeBlocked` -> `NeedsInvestigation` -> `ReadyForUpdate` -> `UpToDate`), then UpdateRing + ClusterName. In-flight + remediation rows surface at the top; Up-to-Date drops to the bottom. (3) Step.05 Not-Ready clusters (review first) table no longer leaves the `Blocking reasons` column as `-` for rows blocked by `UpdateFailed` / `NeedsAttention` / `InProgress` / Warning-only HealthFailure / SbeBlocked - the renderer now derives an actionable token from the Status bucket when the upstream `BlockingReasons` is empty (e.g. `UpdateInProgress (run in-flight)`, `UpdateState=NeedsAttention`, `PrerequisiteRequired (SBE update first)`, `HealthState=Failure (no Critical findings; review Warning findings)`), with `; HealthState=Warning` appended where relevant. (4) Step.10 Detailed Results Description column inline-vs-collapse threshold bumped from 120 to 280 characters, so short single-sentence descriptions render inline and only long multi-line descriptions collapse behind `
view...
`. The previous 120-char cutoff put roughly half the rows inline and half collapsed on the same table, which looked broken. No public API or export-count change (still 60). All bundled pipeline templates bump `GENERATED_AGAINST_MODULE_VERSION` from `'0.8.81'` to `'0.8.82'`. ## Version 0.8.81 - Step summary polish across Steps 05-10. Step.10 fixes the KPI bug where Healthy + Unhealthy did not sum to Total (splits into Cluster Counts + Failing Checks Breakdown tables, adds `OtherClusters` step output; Detailed Results gains Title, raw FailureName and collapsible Description columns surfacing drive/volume detail e.g. file paths from `...FileSystem.Corruption.Correctable`). Steps 05-09 adopt three shared private helpers: `Get-AzLocalStatusIconMap` (host-aware GitHub Unicode vs Azure DevOps shortcodes), `Get-AzLocalClusterPortalLink` (portal deep-link wrapper) and `Get-AzLocalCtrlClickTip` (single-source Ctrl-click banner). Fixes literal shortcode text (`:white_check_mark:` etc.) rendering on Azure DevOps step summaries in Step.08 monitor + Step.09 fleet-update-status. No public API change (still 60 exports). All bundled pipeline templates bump `GENERATED_AGAINST_MODULE_VERSION` from `'0.8.80'` to `'0.8.81'`. @@ -331,7 +333,7 @@ - **TESTS**: parameter-presence regressions for both new switches; new `v0.8.79 Step.7 break-glass` YAML smoke-test context with 8 assertions covering the GHA / ADO input declarations, anti-injection guards, and a sweep of `Resolve-AzLocalPipelineUpdateRing` / `New-AzLocalApplyUpdatesScheduleConfig` / `Get-AzLocalApplyUpdatesScheduleAudit` confirming the override is NOT referenced from any schedule-config code path. - **All bundled pipeline templates** bump `GENERATED_AGAINST_MODULE_VERSION` from `'0.8.78'` to `'0.8.79'`. -## Version 0.8.78 - Patch: pipeline-summary UX polish. (1) JUnit re-classification - `ScheduleBlocked` / `SideloadedBlocked` / `ExcludedByTag` are designed gate-respect outcomes, now render as `` so `dorny/test-reporter` no longer flips Step.07 RED (`HealthCheckBlocked` stays ``). (2) `Add-AzLocalApplyUpdatesStepSummary` gains optional `-UpToDateCount` / `-NotReadyCount` for full ring breakdown in the Readiness KPI table (both `apply-updates.yml` templates wire the upstream `readiness.UpToDateCount` / `readiness.NotReadyCount` outputs through). (3) `actions/download-artifact@v6` -> `@v7` in `apply-updates.yml` (GHA) silences the Node.js 20 deprecation warning. No public API or export-count change (still 60). All bundled pipeline templates bump `GENERATED_AGAINST_MODULE_VERSION` from `'0.8.77'` to `'0.8.78'`. See CHANGELOG.md / docs/release-history.md for the full v0.8.78 entry. +## Version 0.8.78 - Patch: pipeline-summary UX polish (`ScheduleBlocked` / `SideloadedBlocked` / `ExcludedByTag` now JUnit ``; `Add-AzLocalApplyUpdatesStepSummary` gains `-UpToDateCount` / `-NotReadyCount`; `actions/download-artifact@v6` -> `@v7`). See CHANGELOG.md / docs/release-history.md. For full v0.7.x and v0.8.x release notes see: https://github.com/NeilBird/Azure-Local/blob/main/AzLocal.UpdateManagement/CHANGELOG.md diff --git a/AzLocal.UpdateManagement/AzLocal.UpdateManagement.psm1 b/AzLocal.UpdateManagement/AzLocal.UpdateManagement.psm1 index fa0ce749..90c6ac5e 100644 --- a/AzLocal.UpdateManagement/AzLocal.UpdateManagement.psm1 +++ b/AzLocal.UpdateManagement/AzLocal.UpdateManagement.psm1 @@ -151,7 +151,7 @@ Set-StrictMode -Version 1.0 # bumps to one but not the other are caught before release. Two consumers: # - Start-AzLocalClusterUpdate emits this in the run log header. # - Get-AzLocalFleetStatusData stamps it into exported fleet-state JSON. -$script:ModuleVersion = '0.8.82' +$script:ModuleVersion = '0.8.83' $script:DefaultApiVersion = '2025-10-01' $script:DefaultLogFolder = Join-Path -Path $env:ProgramData -ChildPath 'AzLocal.UpdateManagement' diff --git a/AzLocal.UpdateManagement/CHANGELOG.md b/AzLocal.UpdateManagement/CHANGELOG.md index 76904d67..4c566ba1 100644 --- a/AzLocal.UpdateManagement/CHANGELOG.md +++ b/AzLocal.UpdateManagement/CHANGELOG.md @@ -5,6 +5,54 @@ All notable changes to the AzLocal.UpdateManagement module (renamed from AzStack The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.8.83] - 2026-06-15 + +Patch release. Fix-forward for v0.8.82 Item-5: the new Step.08 `UpdateLastAttempt` +reconciliation pass reads `$inv.tags` from `Get-AzLocalClusterInventory`, but the +v0.8.82 inventory projection did not carry the raw ARM `tags` bag. As a result the +"Recent update attempts with no observable updateRun" section was always empty in +production - the reconciliation found zero attempt gaps regardless of fleet +state. v0.8.83 surfaces the raw `tags` bag on every inventory row (in-memory only; +the on-disk CSV / JSON export keeps its explicit `$selectColumns` whitelist so +artefacts continue to omit the raw bag). Also fixes the GH Actions +`monitor-updates.yml` `jobs.outputs:` block to expose `attempts_without_run` to +downstream jobs (the cmdlet was already emitting it in v0.8.82 - only the YAML +wiring was missing), and a docstring drift in `Export-AzLocalUpdateRunMonitorReport` +("6 step outputs" -> "7 step outputs"). No public API change. Export count +unchanged (still 60). + +### Fixed + +- **`Get-AzLocalClusterInventory` PassThru output now carries the raw ARM `tags` + bag** on every cluster row (`$itemProps['tags'] = $cluster.tags`). v0.8.82's + Step.08 `UpdateLastAttempt` reconciliation pass in + `Export-AzLocalUpdateRunMonitorReport` reads `$inv.tags` via + `PSObject.Properties['tags']` to find the audit tag - without this fix, the + reconciliation silently always reported zero gaps. The on-disk CSV / JSON + export remains unchanged: the existing `$selectColumns` whitelist explicitly + names the columns to persist and does NOT include `tags`, so artefacts do + not leak the raw bag. New regression test + (`v0.8.83: Get-AzLocalClusterInventory preserves raw ARM tag bag on PassThru`) + asserts the in-memory property is present AND that `$selectColumns` does not + surface `tags`. +- **GitHub Actions `monitor-updates.yml` `jobs.outputs:` block** now exposes the + `attempts_without_run` step output (`${{ steps.snapshot.outputs.attempts_without_run }}`) + so downstream jobs can gate on `attempts_without_run > 0` (e.g. open a triage + issue when URP package-internal pre-install health-check failures are + detected). The cmdlet was already emitting the step output in v0.8.82; only + the pipeline-level `jobs.outputs:` wiring was missing. Azure DevOps requires + no change here - `Set-AzLocalPipelineOutput` auto-publishes the step variable + via `##vso[task.setvariable variable=...;isOutput=true]`. +- **`Export-AzLocalUpdateRunMonitorReport` cmdlet docstring drift**. The + comment-based help still said "Emits 6 step outputs" after v0.8.82 added the + seventh (`attempts_without_run`). Now reads "Emits 7 step outputs" with + `attempts_without_run` listed alongside the existing six. + +### Changed + +- `GENERATED_AGAINST_MODULE_VERSION` bumped from `'0.8.82'` to `'0.8.83'` + across all bundled GitHub Actions and Azure DevOps pipeline templates. + ## [0.8.82] - 2026-06-15 Patch release. Step.05 + Step.10 step-summary UX polish from the v0.8.81 manual diff --git a/AzLocal.UpdateManagement/Public/Export-AzLocalUpdateRunMonitorReport.ps1 b/AzLocal.UpdateManagement/Public/Export-AzLocalUpdateRunMonitorReport.ps1 index 18dbd982..7423e791 100644 --- a/AzLocal.UpdateManagement/Public/Export-AzLocalUpdateRunMonitorReport.ps1 +++ b/AzLocal.UpdateManagement/Public/Export-AzLocalUpdateRunMonitorReport.ps1 @@ -40,9 +40,9 @@ function Export-AzLocalUpdateRunMonitorReport { threshold line + metric table + 'In-flight runs' table + 'Failed runs (unresolved)' table + action-required / healthy footer) via `Add-AzLocalPipelineStepSummary`. - 6. Emits 6 step outputs via `Set-AzLocalPipelineOutput`: + 6. Emits 7 step outputs via `Set-AzLocalPipelineOutput`: in_flight, long_running, long_running_step, step_errored, - recent_failures, unresolved_failures. + recent_failures, unresolved_failures, attempts_without_run. Internal reuse (per the v0.8.5 thin-YAML consistency contract): * `Get-AzLocalUpdateRuns` (with `-PassThru -SkipSideloadedReset`) diff --git a/AzLocal.UpdateManagement/Public/Get-AzLocalClusterInventory.ps1 b/AzLocal.UpdateManagement/Public/Get-AzLocalClusterInventory.ps1 index 93ff24c3..ca0be149 100644 --- a/AzLocal.UpdateManagement/Public/Get-AzLocalClusterInventory.ps1 +++ b/AzLocal.UpdateManagement/Public/Get-AzLocalClusterInventory.ps1 @@ -336,6 +336,8 @@ function Get-AzLocalClusterInventory { $itemProps['UpdateAuthAccountId'] = if ($authIdTagValue) { $authIdTagValue } else { "" } } $itemProps['ResourceId'] = $cluster.id + # Preserve raw ARM tag bag so downstream consumers can read tags this projection does not explicitly surface (CSV/JSON export below uses an explicit $selectColumns list so this field is excluded from on-disk artefacts). + $itemProps['tags'] = $cluster.tags $inventoryItem = [PSCustomObject]$itemProps $inventory += $inventoryItem } diff --git a/AzLocal.UpdateManagement/README.md b/AzLocal.UpdateManagement/README.md index 27358057..71c707e6 100644 --- a/AzLocal.UpdateManagement/README.md +++ b/AzLocal.UpdateManagement/README.md @@ -2,7 +2,7 @@ > ⚠️ **Disclaimer**: This module is **NOT** a Microsoft supported service offering or product. It is provided as example code only, with no warranty or official support. Refer to the [MIT license](https://github.com/NeilBird/Azure-Local/blob/main/LICENSE) for further information. -**Latest Version:** v0.8.82 - [Published in PowerShell Gallery](https://www.powershellgallery.com/packages/AzLocal.UpdateManagement/0.8.82) +**Latest Version:** v0.8.83 - [Published in PowerShell Gallery](https://www.powershellgallery.com/packages/AzLocal.UpdateManagement/0.8.83) > 📢 **Renamed in v0.7.3**: this module was previously published as `AzStackHci.ManageUpdates`. The new module name aligns with the Azure Local product name (_Microsoft retired the *Azure Stack HCI* brand in late 2024_). The module GUID is preserved across the rename. If you have the old name installed, run: > @@ -23,7 +23,7 @@ Azure Local REST API specification (includes update management endpoints): https **This README (overview + most-recent release notes):** - [Where to Start](#where-to-start) -- [What's New in v0.8.82](#whats-new-in-v0882) +- [What's New in v0.8.83](#whats-new-in-v0883) - [Files](#files) - [Prerequisites](#prerequisites) - [RBAC Requirements](#rbac-requirements) (summary; full reference in [docs/rbac.md](docs/rbac.md)) @@ -86,33 +86,24 @@ If you are new to this module, work through these in order from a regular PowerS > Most CI/CD pipelines in [Automation-Pipeline-Examples/](Automation-Pipeline-Examples/) are direct implementations of one of these workflows. Start there if you want a copy-pasteable end-to-end pipeline. -## What's New in v0.8.82 +## What's New in v0.8.83 -**Patch release. Step.05 + Step.10 step-summary UX polish from the v0.8.81 manual pipeline-run review.** Four small fixes; no public API or export-count change (still 60 exports). +**Patch release. Fix-forward for v0.8.82 Item-5.** The new Step.08 `UpdateLastAttempt` reconciliation pass in `Export-AzLocalUpdateRunMonitorReport` reads `$inv.tags` from `Get-AzLocalClusterInventory`, but the v0.8.82 inventory projection did not carry the raw ARM `tags` bag - so the "Recent update attempts with no observable updateRun" section was silently always empty in production regardless of fleet state. v0.8.83 surfaces the raw `tags` bag on every inventory row (in-memory only; the on-disk CSV / JSON export keeps its explicit `$selectColumns` whitelist so artefacts continue to omit the raw bag). Also fixes the GitHub Actions `monitor-updates.yml` `jobs.outputs:` block to expose `attempts_without_run` to downstream jobs (the cmdlet was already emitting it in v0.8.82 - only the YAML wiring was missing), and a docstring drift in `Export-AzLocalUpdateRunMonitorReport` ("6 step outputs" -> "7 step outputs"). No public API change. Export count unchanged (still 60). -### Step.05 cluster update readiness report +### Fixes -- **Summary counts table no longer duplicates labels.** Each row was reusing the shared `Get-AzLocalStatusIconMap` cell (which already includes its own label, e.g. `Ready for Update`) AND appending a duplicate trailing label, producing `Ready for Update Ready for update` / `Up to Date Up to date` / `Action Required Not ready for update` / `Health Failure Clusters with Critical health failures`. The icon-map cell is now emitted unmodified. The HealthFailure row keeps `(Clusters with Critical health failures)` in parentheses since it counts something different from the readiness cascade. -- **All clusters detail table** now sorts by **Status priority** first (`InProgress` -> `HealthFailure` -> `UpdateFailed` -> `ActionRequired` -> `SbeBlocked` -> `NeedsInvestigation` -> `ReadyForUpdate` -> `UpToDate`), then `UpdateRing` + `ClusterName` as before. In-flight + remediation rows surface at the top of the table; Up-to-Date clusters drop to the bottom so operators see actionable items first. -- **Not-Ready clusters (review first) table - Blocking reasons column** no longer shows `-` for rows blocked by `UpdateFailed` / `NeedsAttention` / `InProgress` / Warning-only `HealthFailure` / `SbeBlocked`. The upstream `Get-AzLocalClusterUpdateReadiness` only populates `BlockingReasons` for `CriticalHealthCheck` findings and abnormal cluster connectivity states (e.g. `NotConnectedRecently`). For every other Not-Ready category the column was left empty and rendered as `-`, forcing operators to cross-read the `Update state` + `Health` columns to infer the reason. The renderer now derives an actionable token from the Status bucket when `BlockingReasons` is empty: - - `InProgress` -> `UpdateInProgress (run in-flight)` - - `UpdateFailed` -> `UpdateState=` (e.g. `UpdateState=NeedsAttention`, `UpdateState=UpdateFailed`) - - `ActionRequired` -> `UpdateState=PreparationFailed` - - `HealthFailure` -> `HealthState=Failure (no Critical findings; review Warning findings)` - - `SbeBlocked` -> `PrerequisiteRequired (SBE update first)` - - `NeedsInvestigation` -> `NeedsInvestigation (no Update or Health signal)` - - For any of the above, `; HealthState=Warning` is appended when the cluster's `HealthState` is `Warning` and the Status bucket is not already `HealthFailure`. - -### Step.10 fleet health status report - -- **Detailed Results - Description column** inline-vs-collapse threshold bumped from 120 to **280 characters**. Short single-sentence descriptions render inline and only long multi-line descriptions collapse behind `
view...
`. The previous 120-char cutoff put roughly half the rows inline and half collapsed on the same table, which looked broken. +- **`Get-AzLocalClusterInventory` PassThru output now carries the raw ARM `tags` bag** on every cluster row. v0.8.82's Step.08 reconciliation reads `$inv.tags` via `PSObject.Properties['tags']` to find the `UpdateLastAttempt` audit tag - without this fix, the reconciliation silently always reported zero gaps. The on-disk CSV / JSON export remains unchanged: the existing `$selectColumns` whitelist explicitly names the columns to persist and does NOT include `tags`, so artefacts do not leak the raw bag. New regression test asserts the in-memory property is present AND that the whitelist still excludes `tags`. +- **GitHub Actions `monitor-updates.yml` `jobs.outputs:` block** now exposes the `attempts_without_run` step output (`${{ steps.snapshot.outputs.attempts_without_run }}`) so downstream jobs can gate on `attempts_without_run > 0` (e.g. open a triage issue when URP package-internal pre-install health-check failures are detected). Azure DevOps requires no change - `Set-AzLocalPipelineOutput` auto-publishes the step variable via `##vso[task.setvariable variable=...;isOutput=true]`; the ADO `monitor-updates.yml` comment-only update brings its docstring in line with the seven step outputs. +- **`Export-AzLocalUpdateRunMonitorReport` cmdlet docstring drift** - now reads "Emits 7 step outputs" with `attempts_without_run` listed alongside the existing six (was still "6 step outputs" after v0.8.82 added the seventh). ### Notes - **No new exports** (count unchanged at 60). -- **`GENERATED_AGAINST_MODULE_VERSION`** bumped from `0.8.81` to `0.8.82` across all bundled pipeline templates. +- **`GENERATED_AGAINST_MODULE_VERSION`** bumped from `0.8.82` to `0.8.83` across all bundled pipeline templates. + +> Previous release notes have moved into the [Release History](#release-history) appendix at the bottom of this document. -See [CHANGELOG.md](CHANGELOG.md#0882---2026-06-15) for the full v0.8.82 entry. See [`What's New in v0.8.81`](docs/release-history.md#whats-new-in-v0881) in the Release History for the previous release. +See [CHANGELOG.md](CHANGELOG.md#0883---2026-06-15) for the full v0.8.83 entry. See [`What's New in v0.8.82`](#whats-new-in-v0882) in the Release History for the previous release. ## Files @@ -591,7 +582,11 @@ This code is provided as-is for educational and reference purposes. The full What's-New history (v0.7.81 and earlier) has moved to [docs/release-history.md](docs/release-history.md). -The most recent release notes for **v0.8.82** stay above under [`What's New in v0.8.82`](#whats-new-in-v0882). +The most recent release notes for **v0.8.83** stay above under [`What's New in v0.8.83`](#whats-new-in-v0883). + +### What's New in v0.8.82 + +**Patch release. Step.05 + Step.10 step-summary UX polish from the v0.8.81 manual pipeline-run review.** Four small fixes; no public API or export-count change (still 60 exports). Step.05 Summary counts table no longer duplicates row labels (each row was reusing the shared `Get-AzLocalStatusIconMap` cell - which already includes its own label - AND appending a duplicate trailing label). Step.05 All clusters detail table sorts by **Status priority** first (`InProgress` -> `HealthFailure` -> `UpdateFailed` -> `ActionRequired` -> `SbeBlocked` -> `NeedsInvestigation` -> `ReadyForUpdate` -> `UpToDate`), then `UpdateRing` + `ClusterName`; Up-to-Date drops to the bottom so operators see actionable items first. Step.05 Not-Ready clusters Blocking reasons column derives an actionable token from the Status bucket when upstream `BlockingReasons` is empty (`UpdateInProgress (run in-flight)`, `UpdateState=`, `PrerequisiteRequired (SBE update first)`, `NeedsInvestigation (no Update or Health signal)`, etc.; appends `; HealthState=Warning` when relevant). Step.10 Detailed Results Description column inline-vs-collapse threshold bumped from 120 to 280 characters in `Export-AzLocalFleetHealthStatusReport`. `GENERATED_AGAINST_MODULE_VERSION` bumped from `0.8.81` to `0.8.82` across all bundled pipeline templates. See [CHANGELOG.md](CHANGELOG.md#0882---2026-06-15) for the full v0.8.82 entry and [docs/release-history.md](docs/release-history.md#whats-new-in-v0882) for the archived entry. ### What's New in v0.8.81 diff --git a/AzLocal.UpdateManagement/Tests/AzLocal.UpdateManagement.Tests.ps1 b/AzLocal.UpdateManagement/Tests/AzLocal.UpdateManagement.Tests.ps1 index 07ecf512..9eaa3741 100644 --- a/AzLocal.UpdateManagement/Tests/AzLocal.UpdateManagement.Tests.ps1 +++ b/AzLocal.UpdateManagement/Tests/AzLocal.UpdateManagement.Tests.ps1 @@ -34,8 +34,8 @@ Describe 'Module: AzLocal.UpdateManagement' { $script:ModuleInfo | Should -Not -BeNullOrEmpty } - It 'Should have version 0.8.82' { - $script:ModuleInfo.Version | Should -Be '0.8.82' + It 'Should have version 0.8.83' { + $script:ModuleInfo.Version | Should -Be '0.8.83' } It 'Module version constants are in sync between .psm1 and .psd1' { @@ -1214,6 +1214,76 @@ Describe 'Regression: Get-AzLocalClusterInventory ValidatePattern shadowing' { } } +Describe 'v0.8.83: Get-AzLocalClusterInventory preserves raw ARM tag bag on PassThru' { + # v0.8.82 shipped Export-AzLocalUpdateRunMonitorReport's UpdateLastAttempt + # reconciliation pass, which reads $inv.tags via PSObject.Properties['tags'] + # to find the audit tag. The Get-AzLocalClusterInventory projection in + # v0.8.82 didn't carry .tags, so the reconciliation was silently always-empty + # in production. v0.8.83 surfaces the raw .tags bag on every inventory row. + # In-memory only - the on-disk CSV/JSON export uses an explicit $selectColumns + # whitelist that excludes .tags, so this regression test also confirms that + # exported CSV does NOT contain a 'tags' column. + + It 'PassThru output exposes raw ARM tags property on every cluster row' { + InModuleScope AzLocal.UpdateManagement { + function global:az { + param() + $global:LASTEXITCODE = 0 + if ($args -contains 'show') { + return '{"id":"00000000-0000-0000-0000-000000000000","name":"sub"}' + } + return '{}' + } + Mock Test-AzCliAvailable { return $true } + Mock Install-AzGraphExtension { return $true } + Mock Invoke-AzResourceGraphQuery { + return @( + [PSCustomObject]@{ + id = '/subscriptions/s/resourceGroups/r/providers/Microsoft.AzureStackHCI/clusters/c1' + name = 'c1' + resourceGroup = 'r' + subscriptionId = 's' + tags = @{ + UpdateRing = 'Wave1' + UpdateLastAttempt = '2026-06-14T21:28:23Z;UpdateStarted;Solution12.2605.1003.210;Update initiated successfully' + UpdateWindow = 'Mon|18-22' + } + }, + [PSCustomObject]@{ + id = '/subscriptions/s/resourceGroups/r/providers/Microsoft.AzureStackHCI/clusters/c2' + name = 'c2' + resourceGroup = 'r' + subscriptionId = 's' + tags = $null + } + ) + } + + $result = Get-AzLocalClusterInventory -PassThru + $result | Should -HaveCount 2 + $c1 = $result | Where-Object ClusterName -eq 'c1' + $c1.PSObject.Properties['tags'] | Should -Not -BeNullOrEmpty + $c1.tags | Should -Not -BeNullOrEmpty + $c1.tags['UpdateRing'] | Should -Be 'Wave1' + $c1.tags['UpdateLastAttempt'] | Should -Be '2026-06-14T21:28:23Z;UpdateStarted;Solution12.2605.1003.210;Update initiated successfully' + $c1.tags['UpdateWindow'] | Should -Be 'Mon|18-22' + + # Untagged cluster still gets a .tags property; value is $null/empty bag. + $c2 = $result | Where-Object ClusterName -eq 'c2' + $c2.PSObject.Properties['tags'] | Should -Not -BeNullOrEmpty + } + } + + It 'CSV/JSON export uses explicit $selectColumns whitelist that excludes the raw tags bag' { + $src = Get-Content -Raw -LiteralPath "$PSScriptRoot/../Public/Get-AzLocalClusterInventory.ps1" + # The whitelist must exist and must NOT name 'tags' (would leak the raw bag to artefacts). + $src | Should -Match '\$selectColumns\s*=' + $whitelistMatch = [regex]::Match($src, '\$selectColumns\s*=\s*@\(([\s\S]*?)\)') + $whitelistMatch.Success | Should -BeTrue -Because 'Get-AzLocalClusterInventory must declare an explicit $selectColumns whitelist for CSV/JSON export' + $whitelistMatch.Groups[1].Value | Should -Not -Match "'tags'" + } +} + Describe 'Function: Set-AzLocalClusterUpdateRingTag' { Context 'Parameter Validation' { diff --git a/AzLocal.UpdateManagement/docs/release-history.md b/AzLocal.UpdateManagement/docs/release-history.md index 9c6a17c9..9edd9e6d 100644 --- a/AzLocal.UpdateManagement/docs/release-history.md +++ b/AzLocal.UpdateManagement/docs/release-history.md @@ -4,7 +4,15 @@ > > **For older releases**, this is the canonical reference; the main README intentionally stays slim so the most recent block is easy to find. > -> **For v0.8.82 (the current release)**, see the main [README.md](../README.md#whats-new-in-v0882) `What's New in v0.8.82` section. +> **For v0.8.83 (the current release)**, see the main [README.md](../README.md#whats-new-in-v0883) `What's New in v0.8.83` section. + +--- + +### What's New in v0.8.83 + +Patch release. Fix-forward for v0.8.82 Item-5. The new Step.08 `UpdateLastAttempt` reconciliation pass in `Export-AzLocalUpdateRunMonitorReport` reads `$inv.tags` from `Get-AzLocalClusterInventory`, but the v0.8.82 inventory projection did not carry the raw ARM `tags` bag, so the "Recent update attempts with no observable updateRun" section silently always reported zero gaps in production. v0.8.83 surfaces the raw `tags` bag on every inventory row (in-memory only; the on-disk CSV / JSON export keeps its explicit `$selectColumns` whitelist so artefacts continue to omit the raw bag). Also fixes the GitHub Actions `monitor-updates.yml` `jobs.outputs:` block to expose `attempts_without_run` to downstream jobs (the cmdlet was already emitting it in v0.8.82 - only the YAML wiring was missing), and a docstring drift in `Export-AzLocalUpdateRunMonitorReport` ("6 step outputs" -> "7 step outputs"). No public API change. Export count unchanged (still 60). + +`GENERATED_AGAINST_MODULE_VERSION` bumped from `0.8.82` to `0.8.83` across all bundled pipeline templates. ---