-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathassess-update-readiness.yml
More file actions
228 lines (213 loc) · 10.8 KB
/
Copy pathassess-update-readiness.yml
File metadata and controls
228 lines (213 loc) · 10.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# AZLOCAL-PIPELINE-ID: assess-update-readiness
# Assess Update Readiness (Pre-flight go/no-go gate)
# --------------------------------------------------
# Runs Get-AzLocalClusterUpdateReadiness and Test-AzLocalClusterHealth -BlockingOnly
# against a target UpdateRing (or the whole fleet) BEFORE apply-updates.yml runs.
#
# Outputs two JUnit XML diagnostic artifacts consumed by the Actions test reporter:
# - readiness.xml (one test per cluster; fails if ReadyForUpdate = $false)
# - health-blocking.xml (one test per cluster; fails if any Critical health failure exists)
#
# CSV artifacts are also attached for spreadsheet triage:
# - readiness.csv
# - health-blocking.csv
#
# This workflow is REPORT-ONLY and always succeeds. It surfaces not-ready / unhealthy
# clusters via the JUnit diagnostic output (Checks tab), the CSV artifacts, and the step
# summary - but it does NOT block downstream runs. In large fleets, day-to-day
# environmental issues (transient storage noise, a single node out, etc.) routinely affect
# a small subset of clusters; blocking the entire wave for one unhealthy cluster is rarely
# the desired behavior. apply-updates.yml is itself per-cluster scoped, so clusters that
# are not ready will simply no-op there too.
#
# If you want a hard pass/fail signal for a chained gate, read the job outputs
# (not_ready, critical_failures) from a downstream workflow via `workflow_run` and apply
# your own tolerance threshold there.
#
# Remediation of Critical health failures is out of scope for this module - see the module
# README "Assess Readiness and Health BEFORE Applying Updates" section for pointers.
#
# v0.8.5 thin-YAML: the inline run: | body (inventory + scope-param construction +
# Get-AzLocalClusterUpdateReadiness + Test-AzLocalClusterHealth + combined JUnit XML
# merge + 8-section markdown summary + step outputs) is now the
# Export-AzLocalClusterUpdateReadinessReport Public cmdlet. This yml is condensed
# to a few lines per step; the full workload (and its Pester tests) live in the module.
# Workflow name carries the same Step.N - prefix as the filename so the GitHub
# Actions sidebar (which sorts workflows alphabetically by this `name:` field)
# lists the eight pipelines in execution order.
name: Step.05 - Assess Update Readiness
on:
# BEGIN-AZLOCAL-CUSTOMIZE:schedule-triggers
# Content between BEGIN/END markers is preserved by
# Update-AzLocalPipelineExample across module upgrades.
schedule:
- cron: '0 7 * * *' # Daily at 07:00 UTC
# END-AZLOCAL-CUSTOMIZE:schedule-triggers
workflow_dispatch:
inputs:
scope:
description: 'Scope of clusters to check'
required: true
default: 'by-update-ring'
type: choice
options:
- 'by-update-ring'
- 'all'
update_ring:
# accepts a single ring (Wave1), a semicolon-delimited list (Prod;Ring2),
# or '***' (three stars - deliberate, not a typo) to match every cluster that HAS the UpdateRing tag set.
description: "UpdateRing tag value (only used when scope=by-update-ring). Single ring, 'Prod;Ring2', or '***'."
required: false
default: 'Wave1'
module_version:
description: 'Pin AzLocal.UpdateManagement version (empty = latest from PSGallery). See Automation-Pipeline-Examples/README.md section 5 "Optional configuration".'
required: false
default: ''
env:
# Module version this workflow YAML was generated against. The install step compares
# 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'
# 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).
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,
# azure/login, dorny/test-reporter, etc). Per GitHub's 2025-09-19 deprecation
# notice Node 20 is forced off by default on 2026-06-16 and removed from the
# runner on 2026-09-16. Setting this env var silences the deprecation warnings
# and exercises Node 24 ahead of the cut-over. To temporarily opt back out,
# set ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true.
# https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
assess:
name: Assess readiness + blocking health
runs-on: windows-latest
permissions:
id-token: write
contents: read
checks: write
outputs:
# v0.8.5 thin-YAML: keys align byte-for-byte with the lowercase output
# names emitted by Export-AzLocalClusterUpdateReadinessReport (which uses
# Set-AzLocalPipelineOutput under the hood). ADO is case-sensitive on
# task.setvariable lookups; GH is case-insensitive but the lowercase
# form is the canonical one used by every v0.8.5+ Step.* cmdlet.
not_ready: ${{ steps.gate.outputs.not_ready }}
critical_failures: ${{ steps.gate.outputs.critical_failures }}
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Azure CLI Login (OIDC)
uses: azure/login@v3
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
# AZURE_SUBSCRIPTION_ID is a repository *Variable* (vars.*), not a Secret. It
# is consumed ONLY here: azure/login@v3 runs `az account set --subscription
# <id>` after the OIDC token exchange so the runner has a default
# `az account` context. It is NOT used to scope Azure Resource Graph queries
# (those run fleet-wide across every subscription the federated identity can
# read) and is NOT interpolated into Azure portal deep-link URLs (those use
# the per-row `subscriptionId` returned by ARG).
# Set it via: gh variable set AZURE_SUBSCRIPTION_ID --body <subId>
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
- name: Install Azure CLI Resource Graph Extension
shell: pwsh
run: az extension add --name resource-graph --yes
- name: Install AzLocal.UpdateManagement from PSGallery
# v0.8.5 thin-YAML: drift detection + banner + step outputs are all
# produced by Add-AzLocalPipelineVersionBanner (Public cmdlet).
shell: pwsh
id: module-version
run: |
$ErrorActionPreference = 'Stop'
$installArgs = @{ Name = 'AzLocal.UpdateManagement'; Scope = 'CurrentUser'; Force = $true; AllowClobber = $true }
if ($env:REQUIRED_MODULE_VERSION) {
$installArgs.RequiredVersion = $env:REQUIRED_MODULE_VERSION
Write-Host "REQUIRED_MODULE_VERSION is set - pinning install to v$($env:REQUIRED_MODULE_VERSION)."
} else {
Write-Host "REQUIRED_MODULE_VERSION is empty - installing the latest version from PSGallery (default fix-forward behaviour)."
}
Install-Module @installArgs
Import-Module AzLocal.UpdateManagement -Force
Add-AzLocalPipelineVersionBanner `
-GeneratedAgainstVersion $env:GENERATED_AGAINST_MODULE_VERSION `
-PinnedVersion $env:REQUIRED_MODULE_VERSION
- name: Run readiness + blocking health checks
# v0.8.5 thin-YAML: the inline run block (inventory + scope param
# construction + Get-AzLocalClusterUpdateReadiness CSV/XML +
# Test-AzLocalClusterHealth -BlockingOnly CSV/XML + combined JUnit
# merge + 8-section markdown step summary + the two step outputs
# (NOT_READY / CRITICAL_FAILURES, now lowercase not_ready /
# critical_failures) has been condensed into the Public cmdlet
# Export-AzLocalClusterUpdateReadinessReport. The cmdlet writes
# ./artifacts/{readiness,health-blocking,assess-readiness}.{csv,xml},
# emits the markdown summary via GITHUB_STEP_SUMMARY, and sets the
# two step outputs.
id: gate
shell: pwsh
env:
INPUT_SCOPE: ${{ github.event.inputs.scope }}
INPUT_UPDATE_RING: ${{ github.event.inputs.update_ring }}
INSTALLED_MODULE_VERSION: ${{ steps.module-version.outputs.installed_module_version }}
run: |
$ErrorActionPreference = 'Stop'
Import-Module AzLocal.UpdateManagement -Force
$params = @{
Scope = if ($env:INPUT_SCOPE) { $env:INPUT_SCOPE } else { 'all' }
OutputDirectory = './artifacts'
InstalledModuleVersion = $env:INSTALLED_MODULE_VERSION
}
if ($env:INPUT_UPDATE_RING) { $params['UpdateRing'] = $env:INPUT_UPDATE_RING }
Export-AzLocalClusterUpdateReadinessReport @params
- name: Compute Artifact Timestamp
if: always()
id: artifact-stamp
shell: pwsh
# every downloadable artifact gets a UTC timestamp suffix so multiple runs on
# the same day produce distinct zip names.
run: |
$stamp = (Get-Date).ToUniversalTime().ToString('yyyyMMdd_HHmmss')
"timestamp=$stamp" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
Write-Host "Artifact timestamp: $stamp"
- name: Upload artifacts
if: always()
uses: actions/upload-artifact@v6
with:
name: azlocal-step.5-readiness-assessment-report_${{ steps.artifact-stamp.outputs.timestamp }}
path: ./artifacts/*
retention-days: 30
- name: Publish combined readiness assessment (primary)
if: always()
uses: dorny/test-reporter@v3
with:
name: 'Update Readiness Assessment'
path: ./artifacts/assess-readiness.xml
reporter: java-junit
list-suites: failed
list-tests: failed
continue-on-error: true
- name: Publish readiness JUnit diagnostics
if: always()
uses: dorny/test-reporter@v3
with:
name: '[JUnit Debug] Readiness (one test per cluster)'
path: ./artifacts/readiness.xml
reporter: java-junit
list-suites: failed
list-tests: failed
continue-on-error: true
- name: Publish blocking-health JUnit diagnostics
if: always()
uses: dorny/test-reporter@v3
with:
name: '[JUnit Debug] Blocking Health Checks (one test per cluster)'
path: ./artifacts/health-blocking.xml
reporter: java-junit
list-suites: failed
list-tests: failed
continue-on-error: true