Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
5ab12a7
v0.8.7 sideload Group A: auth-map + catalog parsers, catalog helper, …
NeilBird Jun 11, 2026
2d801b6
v0.8.7 sideload Group B: extract Select-AzLocalNextUpdateForCluster (…
NeilBird Jun 11, 2026
4acc4f4
v0.8.7 sideload Group C: KV credential resolver + media download/cach…
NeilBird Jun 11, 2026
f7c9bff
v0.8.7 sideload Group D: shared-state model + stale-heartbeat detecti…
NeilBird Jun 11, 2026
e85e88c
v0.8.7 sideload Group E: WinRM session factory (HTTPS 5986 default), …
NeilBird Jun 11, 2026
e91cd3a
v0.8.7 Group F: sideload public cmdlets (plan, state machine, status …
NeilBird Jun 11, 2026
e4f22fe
v0.8.7 Group R: renumber pipelines for sideload (apply->7, monitor->8…
NeilBird Jun 11, 2026
ec40033
v0.8.7 Group S: Step.1/Step.2 sideload-aware UpdateAuthAccountId tag
NeilBird Jun 11, 2026
4a64bcf
AzLocal.UpdateManagement: Group G - Step.6 sideload pipelines + Step.…
NeilBird Jun 11, 2026
db3545c
AzLocal.UpdateManagement: Group P - de-number pipeline filenames + re…
NeilBird Jun 11, 2026
585b7d7
AzLocal.UpdateManagement: Group H1a - de-number pipeline filename ref…
NeilBird Jun 11, 2026
b37704b
AzLocal.UpdateManagement: Group H1b - sideload (Step.6) docs + table/…
NeilBird Jun 11, 2026
f86206d
Group H3 - v0.8.7 version bump (sideload Step.6 + de-numbered filenames)
NeilBird Jun 11, 2026
1244c62
Group H2 - Pester de-number filename literals + sideload tests + 60-f…
NeilBird Jun 11, 2026
abbaead
AzLocal.UpdateManagement: add mock-based Pester tests for v0.8.7 side…
NeilBird Jun 11, 2026
e0f11a0
AzLocal.UpdateManagement: Step.7 monitor CurrentStep now shows deepes…
NeilBird Jun 11, 2026
a379b30
AzLocal.UpdateManagement: document CurrentStep deepest-step fix in CH…
NeilBird Jun 11, 2026
40b482b
v0.8.7: config/ standardization + sideload catalog schema framework +…
NeilBird Jun 11, 2026
96c7005
v0.8.7: fix strict-mode 'CurrentState cannot be found' in JUnit expor…
NeilBird Jun 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 123 additions & 124 deletions AzLocal.UpdateManagement/Automation-Pipeline-Examples/README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# This is the single source of truth for "which UpdateRing(s) is/are
# eligible to apply updates on a given UTC date". It is consumed by:
#
# * Step.6_apply-updates.yml - reads it at every cron firing and
# * Step.7_apply-updates.yml - reads it at every cron firing and
# resolves the UpdateRingValue (and,
# schema v2+, the AllowedUpdateVersions
# allow-list) to use for that run.
Expand Down Expand Up @@ -59,13 +59,13 @@ cycleAnchorYear: 2026

# ---- AllowedUpdateVersions (schema v2, MANDATORY top-level) -------
# >>> ALLOWED-UPDATE-VERSIONS-V2 <<<
# Fleet-wide allow-list of Azure Local update identifiers that Step.6
# Fleet-wide allow-list of Azure Local update identifiers that Step.7
# (apply-updates) is permitted to install.
#
# Default 'Latest' (case-insensitive) is a reserved sentinel meaning
# 'no constraint - install the latest Ready update on each cluster'
# (the historic v0.7.88 default). Leave it as 'Latest' to keep that
# behaviour. When set to an explicit list, Step.6 only installs
# behaviour. When set to an explicit list, Step.7 only installs
# updates whose `name` OR `properties.version` is an EXACT
# (case-insensitive) match for one of the entries. If a cluster has
# no Ready update that matches, that cluster is SKIPPED with status
Expand Down Expand Up @@ -98,7 +98,7 @@ cycleAnchorYear: 2026
# customer ask in v0.7.89): install ONLY the YY04 + YY10 feature
# updates plus the cumulative updates immediately preceding each
# feature update (YY03 + YY09). With v2 schema you list those four
# update names here once and Step.6 ignores every other 'Ready'
# update names here once and Step.7 ignores every other 'Ready'
# update. Example using real Azure Local update names:
# allowedUpdateVersions: 'Solution12.2603.1002.15;Solution12.2604.1003.1005;Solution12.2609.1002.XX;Solution12.2610.1003.XX'
#
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
# AZLOCAL-PIPELINE-ID: apply-updates-schedule-audit
# Step.3 - Apply-Updates Schedule Coverage Audit Pipeline
# This pipeline runs the read-only Test-AzLocalApplyUpdatesScheduleCoverage advisor
# weekly to detect drift between the cron schedule(s) in your apply-updates pipeline
# YAML and the UpdateStartWindow tags that operators have placed on Azure Local clusters.
#
# WHY THIS PIPELINE EXISTS
# ------------------------
# Step.6_apply-updates.yml ships with NO default schedule (trigger:none) on purpose, so
# Step.7_apply-updates.yml ships with NO default schedule (trigger:none) on purpose, so
# customers must consciously choose when updates fire. After you have configured
# UpdateStartWindow tags on your clusters and added cron entries to Step.6_apply-updates.yml, this
# UpdateStartWindow tags on your clusters and added cron entries to Step.7_apply-updates.yml, this
# pipeline is the safety net that catches:
# - a new ring tagged with UpdateStartWindow that no cron in Step.6_apply-updates.yml will reach
# - a new ring tagged with UpdateStartWindow that no cron in Step.7_apply-updates.yml will reach
# - an UpdateStartWindow tag value that fails to parse (typo / wrong syntax)
# - Step.6_apply-updates.yml cron entries that the advisor cannot reason about
# - Step.7_apply-updates.yml cron entries that the advisor cannot reason about
# (DayOfMonth restrictions, step values) so you can audit them manually
#
# REPORTS GENERATED:
Expand Down Expand Up @@ -43,14 +44,14 @@ schedules:

parameters:
- name: pipelinePath
displayName: 'Path (file or folder) to Step.6_apply-updates.yml to audit, repo-relative. REQUIRED so the Recommend view can diff its proposed crons against what is already present in Step.6 and only emit a snippet for the truly missing entries. Default `.azure-pipelines` matches the standard Azure DevOps consumer layout; for repos that keep Step.6_apply-updates.yml at the repo root pass `.`'
displayName: 'Path (file or folder) to Step.7_apply-updates.yml to audit, repo-relative. REQUIRED so the Recommend view can diff its proposed crons against what is already present in Step.7 and only emit a snippet for the truly missing entries. Default `.azure-pipelines` matches the standard Azure DevOps consumer layout; for repos that keep Step.7_apply-updates.yml at the repo root pass `.`'
type: string
default: '.azure-pipelines'

- name: schedulePath
displayName: 'Path to apply-updates-schedule.yml. When set, the audit also reports RingMissingFromSchedule (fleet ring with no schedule row) and RingOrphanedInSchedule (schedule ring no cluster carries). Leave empty to skip the schedule-file two-way diff.'
type: string
default: './apply-updates-schedule.yml'
default: './config/apply-updates-schedule.yml'

- name: leadTimeMinutes
displayName: 'Minutes before UpdateStartWindow opens that the pipeline should fire (0-60)'
Expand Down Expand Up @@ -86,9 +87,14 @@ parameters:
default: false

variables:
GENERATED_AGAINST_MODULE_VERSION: '0.8.6'
GENERATED_AGAINST_MODULE_VERSION: '0.8.7'
REQUIRED_MODULE_VERSION: '${{ parameters.moduleVersion }}'
reportsPath: '$(Build.ArtifactStagingDirectory)/reports'
# v0.8.7 sideload advisor defaults. Override at the pipeline / variable-group
# level (set SIDELOAD_UPDATES=true to enable the recommended-sideload-schedule
# section). Defaults keep the audit byte-identical to v0.8.6 when unset.
SIDELOAD_UPDATES: 'false'
SIDELOAD_LEAD_DAYS: '7'

stages:
- stage: ScheduleCoverage
Expand Down Expand Up @@ -156,6 +162,10 @@ stages:
INPUT_DEBUG: ${{ lower(parameters.debug) }}
INSTALLED_MODULE_VERSION: $(moduleVersion.installed_module_version)
REPORTS_PATH: $(reportsPath)
# v0.8.7 sideload advisor (opt-in). When SIDELOAD_UPDATES=true the audit
# appends a "Recommended sideload schedule" section. Inert when unset/false.
SIDELOAD_UPDATES: $(SIDELOAD_UPDATES)
SIDELOAD_LEAD_DAYS: $(SIDELOAD_LEAD_DAYS)
inputs:
# Replace with your service connection name
azureSubscription: 'AzureLocal-ServiceConnection' # Update with your service connection name
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Step.6 - Apply Updates to Azure Local Clusters
# AZLOCAL-PIPELINE-ID: apply-updates
# Step.7 - Apply Updates to Azure Local Clusters
# This pipeline applies updates to clusters filtered by UpdateRing tag value

# BEGIN-AZLOCAL-CUSTOMIZE:schedule-triggers
# PREFERRED PATTERN - drive automated update runs from a ring-aware
# schedule file (apply-updates-schedule.yml at the repo root by default),
# schedule file (config/apply-updates-schedule.yml by default),
# not from per-window crons.
#
# How it works:
Expand All @@ -20,7 +21,7 @@
# New-AzLocalApplyUpdatesScheduleConfig.
#
# Generate a STRAWMAN schedule file from your fleet via:
# New-AzLocalApplyUpdatesScheduleConfig -OutputPath .\apply-updates-schedule.yml
# New-AzLocalApplyUpdatesScheduleConfig -OutputPath .\config\apply-updates-schedule.yml
# Every row is commented out by default - review and uncomment to activate.
#
# Concurrency: ADO has no first-class YAML concurrency block; use Pipeline
Expand Down Expand Up @@ -114,18 +115,20 @@ 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.6'
value: '0.8.7'
# 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).
- name: REQUIRED_MODULE_VERSION
value: '${{ parameters.moduleVersion }}'
# Path to the ring-aware apply-updates-schedule.yml (relative to repo
# root). The 'Resolve UpdateRing from schedule' task reads this file when
# $(Build.Reason)='Schedule'. Override at queue time if you keep the schedule
# outside the repo root.
# $(Build.Reason)='Schedule'. Default 'config/apply-updates-schedule.yml'
# matches where Copy-AzLocalPipelineExample drops the starter, alongside
# config/ClusterUpdateRings.csv. Override at queue time if you keep the
# schedule elsewhere.
- name: APPLY_UPDATES_SCHEDULE_PATH
value: './apply-updates-schedule.yml'
value: './config/apply-updates-schedule.yml'
# ITSM connector. Uncomment the group reference once you
# have created a variable group named 'AzureLocal-ITSM-Secrets' containing:
# ITSM_SN_INSTANCE_URL, ITSM_SN_CLIENT_ID, ITSM_SN_CLIENT_SECRET
Expand Down Expand Up @@ -230,7 +233,7 @@ stages:
displayName: 'Publish Readiness Report'
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'azlocal-step.6-apply-updates-readiness-report_$(stamp.artifactStamp)'
ArtifactName: 'azlocal-step.7-apply-updates-readiness-report_$(stamp.artifactStamp)'
publishLocation: 'Container'

# Stage 2: Apply Updates (with approval gate for production)
Expand Down Expand Up @@ -324,7 +327,7 @@ stages:
source: 'current'
# artifact name now carries a UTC timestamp - rebuild via the
# readinessArtifactStamp variable mapped from the CheckReadiness stage.
artifact: 'azlocal-step.6-apply-updates-readiness-report_$(readinessArtifactStamp)'
artifact: 'azlocal-step.7-apply-updates-readiness-report_$(readinessArtifactStamp)'
path: '$(Build.ArtifactStagingDirectory)'

# v0.8.5 thin-YAML - readiness-gated apply via shared helper. Validates
Expand Down Expand Up @@ -379,7 +382,7 @@ stages:
displayName: 'Publish Update Logs'
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'azlocal-step.6-apply-updates-logs_$(stamp.artifactStamp)'
ArtifactName: 'azlocal-step.7-apply-updates-logs_$(stamp.artifactStamp)'
publishLocation: 'Container'

- task: PublishTestResults@2
Expand Down Expand Up @@ -447,7 +450,7 @@ stages:
condition: and(always(), eq('${{ parameters.raiseItsmTicket }}', 'true'))
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'azlocal-step.6-apply-updates-itsm-results_$(stamp.artifactStamp)'
ArtifactName: 'azlocal-step.7-apply-updates-itsm-results_$(stamp.artifactStamp)'
publishLocation: 'Container'
continueOnError: true

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# AZLOCAL-PIPELINE-ID: assess-update-readiness
# Step.5 - 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 Step.6_apply-updates.yml runs.
# against a target UpdateRing (or the whole fleet) BEFORE Step.7_apply-updates.yml runs.
#
# Publishes two JUnit XML diagnostic results to the Azure DevOps Tests tab:
# - readiness.xml (one test per cluster; fails if ReadyForUpdate = $false)
Expand All @@ -14,7 +15,7 @@
# 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. Step.6_apply-updates.yml is itself per-cluster scoped, so clusters that are not
# behavior. Step.7_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 output variables
Expand Down Expand Up @@ -69,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.6'
GENERATED_AGAINST_MODULE_VERSION: '0.8.7'
# 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).
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# AZLOCAL-PIPELINE-ID: authentication-test
# Step.0 - Authentication Validation and Subscription Scope Report
#
# PURPOSE:
Expand Down Expand Up @@ -59,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.6'
value: '0.8.7'
# 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).
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# AZLOCAL-PIPELINE-ID: fleet-connectivity-status
# Step.4 - Fleet Connectivity Status Monitoring Pipeline
# This pipeline surfaces network-connectivity issues across every Azure Local
# cluster the service connection can read - four scopes in one pipeline:
Expand All @@ -12,7 +13,7 @@
# - Find clusters where Arc agent or ARB has dropped off and updates / management calls will fail
# - Catch Disconnected physical NICs that still have a real IP (likely cable / driver / port issues -
# Disconnected adapters with no IP or APIPA self-assignment are intentionally excluded as noise)
# - Triage hand-off into ITSM (wired identically to Step.8 / Step.9 - opt-in via parameter)
# - Triage hand-off into ITSM (wired identically to Step.9 / Step.10 - opt-in via parameter)
#
# REPORTS GENERATED:
# - Pipeline run summary (uploaded via ##vso[task.uploadsummary]): one section per
Expand Down Expand Up @@ -56,7 +57,7 @@ name: Step.4 - Fleet Connectivity Status
trigger: none # Manual or scheduled only

schedules:
# Daily at 05:30 UTC - offset 30 min ahead of Step.8 (06:00) and Step.9 (07:00)
# Daily at 05:30 UTC - offset 30 min ahead of Step.9 (06:00) and Step.10 (07:00)
# so the three fleet-scope read pipelines stagger their ARG calls.
- cron: '30 5 * * *'
displayName: 'Daily Fleet Connectivity Check'
Expand Down Expand Up @@ -109,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.6'
GENERATED_AGAINST_MODULE_VERSION: '0.8.7'
# 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).
Expand All @@ -121,7 +122,7 @@ variables:
# named 'AzureLocal-ITSM-Secrets'. ADO does not allow mixing mapping-form
# variables with '- group:' entries in the same block, so to enable the
# group reference convert this entire 'variables:' block to list form (see
# Step.9 for the conversion recipe). When raiseItsmTicket=false the Raise
# Step.10 for the conversion recipe). When raiseItsmTicket=false the Raise
# ITSM task is skipped and the secrets are not consulted.

stages:
Expand Down Expand Up @@ -228,7 +229,7 @@ stages:
# ----------------------------------------------------------------------
# ITSM Connector (ServiceNow auto-raise on connectivity failures)
# Opt-in (gated on parameters.raiseItsmTicket == true). Mirrors the
# Step.8 / Step.9 wiring - reads the JUnit file produced above, uses
# Step.9 / Step.10 wiring - reads the JUnit file produced above, uses
# the same azurelocal-itsm.yml trigger matrix, same dedupe key.
# ----------------------------------------------------------------------
- task: PowerShell@2
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Step.9 - Fleet Health Status Monitoring Pipeline
# AZLOCAL-PIPELINE-ID: fleet-health-status
# Step.10 - Fleet Health Status Monitoring Pipeline
# This pipeline surfaces 24-hour system health-check failures across every Azure Local
# cluster the service connection can read - including clusters that are already
# "up to date" with no available updates. The 24-hour health checks continue to run
Expand Down Expand Up @@ -104,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.6'
GENERATED_AGAINST_MODULE_VERSION: '0.8.7'
# 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).
Expand Down Expand Up @@ -217,7 +218,7 @@ stages:
condition: always()
inputs:
PathtoPublish: '$(reportsPath)'
ArtifactName: 'azlocal-step.9-fleet-health-status-report_$(stamp.artifactStamp)'
ArtifactName: 'azlocal-step.10-fleet-health-status-report_$(stamp.artifactStamp)'
publishLocation: 'Container'

# Publish JUnit test results LAST so the Test Results tab is populated but the
Expand Down Expand Up @@ -321,7 +322,7 @@ stages:
condition: and(always(), eq('${{ parameters.raiseItsmTicket }}', 'true'))
inputs:
PathtoPublish: '$(reportsPath)'
ArtifactName: 'azlocal-step.9-fleet-health-status-itsm-results_$(stamp.artifactStamp)'
ArtifactName: 'azlocal-step.10-fleet-health-status-itsm-results_$(stamp.artifactStamp)'
publishLocation: 'Container'
continueOnError: true

Expand All @@ -331,5 +332,5 @@ stages:
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '$(reportsPath)/itsm-results.xml'
testRunTitle: 'ITSM Tickets (Step.9) - $(Build.BuildNumber)'
testRunTitle: 'ITSM Tickets (Step.10) - $(Build.BuildNumber)'
failTaskOnFailedTests: false
Loading
Loading