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
063a743
v0.8.85: consolidate setup workflow and align pipeline/version metadata
NeilBird Jun 16, 2026
189f964
test-run-timings
NeilBird Jun 16, 2026
5474cd1
v0.8.85 follow-up: delete ADO auth+inventory workflows and add merged…
NeilBird Jun 16, 2026
12328a7
v0.8.85 follow-up: move collapsible subscription section into Export-…
NeilBird Jun 16, 2026
f96c602
v0.8.85 follow-up: fix live integration test failures (Step.0 ErrorAc…
NeilBird Jun 16, 2026
b2bc2c4
Add test-run-timings.csv to repo (test performance metrics)
NeilBird Jun 16, 2026
ebaa587
docs: align pipeline appendix with Setup/Fleet naming
NeilBird Jun 16, 2026
4e21ffc
chore: update test run timings
NeilBird Jun 16, 2026
286d10b
v0.8.85: address PR #91 review findings - manifest, docs, mojibake, p…
NeilBird Jun 16, 2026
715efc7
docs: clarify sideload-catalog.example.yml header comment (v0.8.7 on-…
NeilBird Jun 16, 2026
43b8c86
docs: fix grammar in sideload-catalog.example.yml header (introduce -…
NeilBird Jun 16, 2026
b4a4a3a
Merge remote-tracking branch 'origin/main' into users/nebird/development
NeilBird Jun 16, 2026
a1d4ff3
v0.8.86: rename onboarding pipelines Setup -> Config + clarify GitHub…
NeilBird Jun 16, 2026
6f01258
ci: disable hosted Pester workflow (comment out pull_request + push t…
NeilBird Jun 16, 2026
25a04bd
Merge remote-tracking branch 'origin/main' into users/nebird/development
NeilBird Jun 16, 2026
fc7bf6c
v0.8.87: rename 'Orphan ARBs' section to 'Non-Azure Local and/or Orph…
NeilBird Jun 16, 2026
c46da8f
v0.8.87: collapse Cluster Connectivity (with ARB Status) table by def…
NeilBird Jun 16, 2026
be7bbfd
v0.8.87: split Cluster Connectivity table into with/without connectiv…
NeilBird Jun 16, 2026
1a3110c
v0.8.87: rename bundled pipeline display names to Config:/Monitor:/Up…
NeilBird Jun 16, 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
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,21 @@ triggers:
raiseTicket: false
NotReady:
raiseTicket: false

# ----- Update: 4 - Monitor In-Flight Updates statuses (v0.8.87) -----
# Emitted by Export-AzLocalUpdateRunMonitorReport in update-monitor.xml.
# Wire monitor-updates.yml with raise_itsm_ticket=true to open tickets for
# these. Dedupe key = SHA256(ClusterResourceId|UpdateName|TriggerCategory),
# so a stuck/attempt-gap cluster opens at most ONE incident until it clears.
AttemptWithoutRun:
raiseTicket: true
severity: 3
category: "Update attempt with no observable updateRun" # UpdateLastAttempt tag set, but no updateRun materialised (commonly a URP package pre-install health-check failure)
StepError:
raiseTicket: true
severity: 2
category: "Update step stuck in error" # progress.status == Error while state is still InProgress
LongRunningOverall:
raiseTicket: false # informational backstop; opt-in if you want a ticket per run exceeding the overall-elapsed threshold
LongRunningStep:
raiseTicket: false # informational; opt-in if you want a ticket per run whose current step exceeds the per-step threshold
110 changes: 57 additions & 53 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 @@ -27,7 +27,7 @@
# A cron firing that lands on a day with NO matching schedule rows is
# logged and exits 0 - no errors, no failures.
#
# This file is REQUIRED for the "Fleet: 04 - Apply Updates"
# This file is REQUIRED for the "Update: 3 - Apply Updates"
# (apply-updates.yml) pipeline when triggered by a schedule (not
# workflow_dispatch). If the file is missing, apply-updates.yml throws
# with the exact remediation command.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# AZLOCAL-PIPELINE-ID: apply-updates-schedule-audit
# Config: 03 - Apply-Updates Schedule Coverage Audit Pipeline
# Config: 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.
Expand Down Expand Up @@ -87,7 +87,7 @@ parameters:
default: false

variables:
GENERATED_AGAINST_MODULE_VERSION: '0.8.86'
GENERATED_AGAINST_MODULE_VERSION: '0.8.87'
REQUIRED_MODULE_VERSION: '${{ parameters.moduleVersion }}'
reportsPath: '$(Build.ArtifactStagingDirectory)/reports'
# v0.8.7 sideload advisor defaults. Override at the pipeline / variable-group
Expand All @@ -98,7 +98,7 @@ variables:

stages:
- stage: ScheduleCoverage
displayName: 'Config: 03 - Audit Apply-Updates Schedule Coverage'
displayName: 'Config: 3 - Audit Apply-Updates Schedule Coverage'
jobs:
- job: Audit
displayName: 'Run Schedule Coverage Advisor'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# AZLOCAL-PIPELINE-ID: apply-updates
# Fleet: 04 - Apply Updates to Azure Local Clusters
# Update: 3 - Apply Updates to Azure Local Clusters
# This pipeline applies updates to clusters filtered by UpdateRing tag value

# Author guidance lives OUTSIDE the customise markers so module refreshes
Expand Down Expand Up @@ -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.86'
value: '0.8.87'
# 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 @@ -252,7 +252,7 @@ stages:

# Stage 2: Apply Updates (with approval gate for production)
- stage: ApplyUpdates
displayName: 'Fleet: 04 - Apply Updates'
displayName: 'Update: 3 - Apply Updates'
dependsOn: CheckReadiness
condition: and(succeeded(), gt(dependencies.CheckReadiness.outputs['ReadinessCheck.readiness.ReadyCount'], 0))

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# AZLOCAL-PIPELINE-ID: assess-update-readiness
# Fleet: 01 - Assess Update Readiness (Pre-flight go/no-go gate)
# Update: 1 - 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.
Expand Down Expand Up @@ -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.86'
GENERATED_AGAINST_MODULE_VERSION: '0.8.87'
# 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 @@ -82,7 +82,7 @@ pool:

stages:
- stage: Assess
displayName: 'Fleet: 01 - Assess Update Readiness'
displayName: 'Update: 1 - Assess Update Readiness'
jobs:
- job: AssessReadiness
displayName: 'Readiness + blocking health gate'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# AZLOCAL-PIPELINE-ID: fleet-connectivity-status
# Fleet: 02 - Fleet Connectivity Status Monitoring Pipeline
# Monitor: 1 - 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 Down Expand Up @@ -49,7 +49,7 @@

# Pipeline display name carries the same Step.N - prefix as the filename so the
# ADO pipelines list (sorts by `name:` field) lists pipelines in execution order.
name: 'Fleet: 02 - Fleet Connectivity Status'
name: 'Monitor: 1 - Fleet Connectivity Status'

# BEGIN-AZLOCAL-CUSTOMIZE:schedule-triggers
# Edits inside this block are preserved across Update-AzLocalPipelineExample
Expand Down Expand Up @@ -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.86'
GENERATED_AGAINST_MODULE_VERSION: '0.8.87'
# 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,5 +1,5 @@
# AZLOCAL-PIPELINE-ID: fleet-health-status
# Fleet: 07 - Fleet Health Status Monitoring Pipeline
# Monitor: 2 - 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 @@ -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.86'
GENERATED_AGAINST_MODULE_VERSION: '0.8.87'
# 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,5 +1,5 @@
# AZLOCAL-PIPELINE-ID: fleet-update-status
# Fleet: 06 - Fleet Update Status Monitoring Pipeline
# Monitor: 3 - Fleet Update Status Monitoring Pipeline
# This pipeline monitors update status across all Azure Local clusters and generates reports
#
# USE CASES:
Expand Down Expand Up @@ -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.86'
GENERATED_AGAINST_MODULE_VERSION: '0.8.87'
# 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,5 +1,5 @@
# AZLOCAL-PIPELINE-ID: manage-updatering-tags
# Config: 02 - Manage UpdateRing Tags
# Config: 2 - Manage UpdateRing Tags
# This pipeline creates or updates UpdateRing tags on Azure Local clusters from a CSV file
#
# WORKFLOW:
Expand Down Expand Up @@ -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.86'
value: '0.8.87'
# 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 @@ -58,7 +58,7 @@ pool:

stages:
- stage: ManageTags
displayName: 'Config: 02 - Manage UpdateRing Tags'
displayName: 'Config: 2 - Manage UpdateRing Tags'
jobs:
- job: ApplyTags
displayName: 'Apply UpdateRing Tags'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# AZLOCAL-PIPELINE-ID: monitor-updates
# Fleet: 05 - Monitor In-Flight Updates (Azure DevOps)
# Update: 4 - Monitor In-Flight Updates (Azure DevOps)
# -----------------------------------------------------------
# Reports clusters whose latest update run is currently in flight, with the
# CURRENT STEP each cluster is on and the ELAPSED DURATION of the run. Built
Expand Down Expand Up @@ -83,10 +83,54 @@ parameters:
type: string
default: ''

# --- ITSM Connector (ServiceNow auto-raise on in-flight monitor failures) ---
# Set raiseItsmTicket=true to open ServiceNow incidents (v0.8.87) from each
# stuck-step / failed / attempt-without-run cluster surfaced in
# update-monitor.xml. Default false so existing schedules stay byte-identical
# until you opt in. The connector reads the JUnit file this pipeline already
# produces (update-monitor.xml) and the trigger matrix in
# ./.itsm/azurelocal-itsm.yml (defaults raise AttemptWithoutRun / StepError /
# Failed; LongRunningStep / LongRunningOverall are opt-in).
- name: raiseItsmTicket
displayName: 'Open ITSM tickets (ServiceNow) for in-flight monitor failures'
type: boolean
default: false

- name: itsmConfigPath
displayName: 'Path to ITSM matrix config (YAML or JSON)'
type: string
default: './.itsm/azurelocal-itsm.yml'

- name: itsmDryRun
displayName: 'ITSM: build payloads + run read-only dedupe but do NOT create tickets'
type: boolean
default: false

- name: itsmForceCreate
displayName: 'ITSM: bypass dedupe and always create new tickets (use with caution)'
type: boolean
default: false

variables:
GENERATED_AGAINST_MODULE_VERSION: '0.8.86'
GENERATED_AGAINST_MODULE_VERSION: '0.8.87'
REQUIRED_MODULE_VERSION: '${{ parameters.moduleVersion }}'
reportsPath: '$(Build.ArtifactStagingDirectory)/reports'
# ITSM connector. When raiseItsmTicket=true the Raise ITSM task below
# references $(ITSM_SN_INSTANCE_URL), $(ITSM_SN_CLIENT_ID),
# $(ITSM_SN_CLIENT_SECRET). The simplest binding is an ADO variable group
# 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:
# variables:
# - name: GENERATED_AGAINST_MODULE_VERSION
# value: '0.8.87'
# - name: REQUIRED_MODULE_VERSION
# value: '${{ parameters.moduleVersion }}'
# - name: reportsPath
# value: '$(Build.ArtifactStagingDirectory)/reports'
# - group: AzureLocal-ITSM-Secrets
# When raiseItsmTicket=false the Raise ITSM task is skipped and the secrets
# are not consulted, so the conversion is only required for opt-in users.

stages:
- stage: MonitorInFlight
Expand Down Expand Up @@ -187,3 +231,103 @@ stages:
testRunTitle: 'In-Flight Update Monitor'
mergeTestResults: false
failTaskOnFailedTests: false

# ----------------------------------------------------------------------
# ITSM Connector (ServiceNow auto-raise on in-flight monitor failures)
#
# Fully opt-in (gated on parameters.raiseItsmTicket == true). Runs AFTER
# the JUnit file is published, so the ITSM task is strictly additive -
# failures here never affect the monitor result (which is always green;
# this is a report-only pipeline). Reads the same update-monitor.xml that
# powers the summary. The JUnit emitter writes per-testcase <properties>
# (ClusterName / ClusterResourceId / UpdateName / Status / CurrentStep /
# ClusterPortalUrl / UpdateRunPortalUrl) so New-AzLocalIncident can compute
# the SHA256 dedupe key (one incident per stuck/failed/attempt-gap cluster
# until it clears) and the Mustache body template can deep-link into the
# Azure portal.
# ----------------------------------------------------------------------
- task: PowerShell@2
displayName: 'Install powershell-yaml (ITSM config parser)'
condition: and(succeeded(), eq('${{ parameters.raiseItsmTicket }}', 'true'))
inputs:
targetType: 'inline'
pwsh: true
script: |
if (-not (Get-Module -ListAvailable -Name powershell-yaml)) {
Install-Module powershell-yaml -Scope CurrentUser -Force -AllowClobber
}

- task: AzureCLI@2
displayName: 'Raise ITSM tickets'
name: itsm
condition: and(succeeded(), eq('${{ parameters.raiseItsmTicket }}', 'true'))
env:
# BEGIN-AZLOCAL-CUSTOMIZE:itsm-secrets
# Bind your ITSM connector secrets from the variable group above.
# Defaults assume the ServiceNow OAuth client_credentials naming used
# by azurelocal-itsm.yml (env://ITSM_SN_*). If your secret names differ,
# update them here - this block is preserved by
# Update-AzLocalPipelineExample across module upgrades.
ITSM_SN_INSTANCE_URL: $(ITSM_SN_INSTANCE_URL)
ITSM_SN_CLIENT_ID: $(ITSM_SN_CLIENT_ID)
ITSM_SN_CLIENT_SECRET: $(ITSM_SN_CLIENT_SECRET)
# END-AZLOCAL-CUSTOMIZE:itsm-secrets
inputs:
azureSubscription: 'AzureLocal-ServiceConnection' # Update with your service connection name
scriptType: 'pscore'
scriptLocation: 'inlineScript'
inlineScript: |
Import-Module AzLocal.UpdateManagement -Force

$configPath = "${{ parameters.itsmConfigPath }}"
$dryRun = [System.Convert]::ToBoolean("${{ parameters.itsmDryRun }}")
$force = [System.Convert]::ToBoolean("${{ parameters.itsmForceCreate }}")

if (-not (Test-Path $configPath)) {
Write-Host "##vso[task.logissue type=warning]ITSM config not found at '$configPath' - skipping ticket creation."
exit 0
}

$cfg = Get-AzLocalItsmConfig -Path $configPath

$junitInput = "$(reportsPath)/update-monitor.xml"
if (-not (Test-Path $junitInput)) {
Write-Host "##vso[task.logissue type=warning]No update-monitor.xml found at '$junitInput' - skipping ticket creation."
exit 0
}

$params = @{
InputArtifactPath = $junitInput
Config = $cfg
RunMetadata = @{
Platform = 'azure-devops'
RunId = $env:BUILD_BUILDID
RunUrl = "$($env:SYSTEM_COLLECTIONURI)$($env:SYSTEM_TEAMPROJECT)/_build/results?buildId=$($env:BUILD_BUILDID)"
Branch = $env:BUILD_SOURCEBRANCH
}
DryRun = $dryRun
ForceCreate = $force
ExportPath = "$(reportsPath)/itsm-results.csv"
ExportJUnitPath = "$(reportsPath)/itsm-results.xml"
}

$results = New-AzLocalIncident @params
$results | Format-Table ClusterName, Action, TicketId, Severity -AutoSize

- task: PublishBuildArtifacts@1
displayName: 'Publish ITSM Artefacts'
condition: and(always(), eq('${{ parameters.raiseItsmTicket }}', 'true'))
inputs:
PathtoPublish: '$(reportsPath)'
ArtifactName: 'azlocal-step.8-update-monitor-itsm-results_$(stamp.artifactStamp)'
publishLocation: 'Container'
continueOnError: true

- task: PublishTestResults@2
displayName: 'Publish ITSM Test Results'
condition: and(always(), eq('${{ parameters.raiseItsmTicket }}', 'true'))
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '$(reportsPath)/itsm-results.xml'
testRunTitle: 'ITSM Tickets (Update: 4 Monitor) - $(Build.BuildNumber)'
failTaskOnFailedTests: false
Loading