diff --git a/.github/workflows/AzLocal.UpdateManagement-pester.yml b/.github/workflows/AzLocal.UpdateManagement-pester.yml index 61e8c43b..6a23b73e 100644 --- a/.github/workflows/AzLocal.UpdateManagement-pester.yml +++ b/.github/workflows/AzLocal.UpdateManagement-pester.yml @@ -10,17 +10,22 @@ name: AzLocal.UpdateManagement - Pester # Cost mitigation: path-filtered to AzLocal.UpdateManagement/** only, and only # fires on the three useful PR activities (opened, synchronize, reopened). +# DISABLED: automatic CI triggers are commented out because the Pester suite is +# run locally before every push (see AzLocal.UpdateManagement/Tests/Invoke-Tests.ps1). +# To re-enable hosted CI, uncomment the pull_request + push blocks below. +# workflow_dispatch is kept so the workflow remains valid (GitHub requires at +# least one trigger) and can still be run on demand from the Actions tab. on: - pull_request: - types: [opened, synchronize, reopened] - paths: - - 'AzLocal.UpdateManagement/**' - - '.github/workflows/AzLocal.UpdateManagement-pester.yml' - push: - branches: [main] - paths: - - 'AzLocal.UpdateManagement/**' - - '.github/workflows/AzLocal.UpdateManagement-pester.yml' + # pull_request: + # types: [opened, synchronize, reopened] + # paths: + # - 'AzLocal.UpdateManagement/**' + # - '.github/workflows/AzLocal.UpdateManagement-pester.yml' + # push: + # branches: [main] + # paths: + # - 'AzLocal.UpdateManagement/**' + # - '.github/workflows/AzLocal.UpdateManagement-pester.yml' workflow_dispatch: concurrency: diff --git a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/README.md b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/README.md index a7934f0c..6a4cb5c0 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/README.md +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/README.md @@ -56,10 +56,10 @@ It is written in the same step-by-step style as [`ITSM/README.md`](../ITSM/READM By the end of this guide you will have: - A federated identity (no client secrets) wired into your CI/CD platform with the **minimum** Azure RBAC needed for cluster update management. -- Setup/Fleet workflows committed to your repo and visible in the Actions / Pipelines UI: - - **Setup: 01 - Validate Auth and Inventory Clusters** (GitHub) - merged auth + inventory flow with a clear setup-first summary (including collapsible subscription details) and cluster inventory export. - - **Setup: 02 - Manage UpdateRing Tags** - bulk-apply `UpdateRing`, `UpdateStartWindow`, `UpdateExclusionsWindow`, `UpdateExcluded` tags from CSV. - - **Setup: 03 - Apply-Updates Schedule Coverage Audit** - read-only audit that validates your schedule against `UpdateStartWindow` tags. +- Config/Fleet workflows committed to your repo and visible in the Actions / Pipelines UI: + - **Config: 01 - Validate Auth and Inventory Clusters** (GitHub) - merged auth + inventory flow with a clear setup-first summary (including collapsible subscription details) and cluster inventory export. + - **Config: 02 - Manage UpdateRing Tags** - bulk-apply `UpdateRing`, `UpdateStartWindow`, `UpdateExclusionsWindow`, `UpdateExcluded` tags from CSV. + - **Config: 03 - Apply-Updates Schedule Coverage Audit** - read-only audit that validates your schedule against `UpdateStartWindow` tags. - **Fleet: 01 - Assess Update Readiness** - pre-flight readiness + blocking-health gate report. - **Fleet: 02 - Fleet Connectivity Status** - Arc connectivity, NIC health, and ARB status snapshot. - **Fleet: 03 - Sideload Updates (opt-in)** - on-prem media pre-stage workflow for disconnected environments. @@ -72,18 +72,18 @@ By the end of this guide you will have: The pipelines are **fully opt-in additive layers** over the module. The PowerShell functions also work without any pipeline at all - see [section 10](#10-standalone-html-report-no-pipeline) for the ad-hoc / desktop story. -### 1.1 Why the pipelines are named `Setup: NN` and `Fleet: NN` +### 1.1 Why the pipelines are named `Config: NN` and `Fleet: NN` The active workflow model uses two clear groups: -- `Setup: 01-03` for onboarding and configuration. +- `Config: 01-03` for onboarding and configuration. - `Fleet: 01-07` for day-2 operational monitoring and update execution. | Group | Workflow name | GH Actions | Azure DevOps | |---|---|---|---| -| Setup | Setup: 01 - Validate Auth and Inventory Clusters | `setup-validate-and-inventory.yml` | `authentication-test.yml` + `inventory-clusters.yml` | -| Setup | Setup: 02 - Manage UpdateRing Tags | `manage-updatering-tags.yml` | `manage-updatering-tags.yml` | -| Setup | Setup: 03 - Apply-Updates Schedule Coverage Audit | `apply-updates-schedule-audit.yml` | `apply-updates-schedule-audit.yml` | +| Config | Config: 01 - Validate Auth and Inventory Clusters | `setup-validate-and-inventory.yml` | `authentication-test.yml` + `inventory-clusters.yml` | +| Config | Config: 02 - Manage UpdateRing Tags | `manage-updatering-tags.yml` | `manage-updatering-tags.yml` | +| Config | Config: 03 - Apply-Updates Schedule Coverage Audit | `apply-updates-schedule-audit.yml` | `apply-updates-schedule-audit.yml` | | Fleet | Fleet: 01 - Assess Update Readiness | `assess-update-readiness.yml` | `assess-update-readiness.yml` | | Fleet | Fleet: 02 - Fleet Connectivity Status | `fleet-connectivity-status.yml` | `fleet-connectivity-status.yml` | | Fleet | Fleet: 03 - Sideload Updates (Opt-in) | `sideload-updates.yml` | `sideload-updates.yml` | @@ -92,13 +92,13 @@ The active workflow model uses two clear groups: | Fleet | Fleet: 06 - Fleet Update Status | `fleet-update-status.yml` | `fleet-update-status.yml` | | Fleet | Fleet: 07 - Fleet Health Status | `fleet-health-status.yml` | `fleet-health-status.yml` | -- **GitHub Actions**: the Actions sidebar sorts workflows alphabetically by the `name:` field. Prefixing names with `Setup: NN` and `Fleet: NN` keeps the sidebar in intended execution order. +- **GitHub Actions**: the Actions sidebar sorts workflows alphabetically by the `name:` field. Prefixing names with `Config: NN` and `Fleet: NN` keeps the sidebar in intended execution order (the `Config:` group sorts ahead of `Fleet:` so onboarding pipelines appear first). - ![GitHub Actions sidebar showing Setup and Fleet workflows in execution order](../docs/images/github-actions-10-pipelines-view.png) + ![GitHub Actions sidebar showing Config and Fleet workflows in execution order](../docs/images/github-actions-10-pipelines-view.png) - *The Setup/Fleet numeric prefixes keep the GitHub Actions sidebar in practical execution order rather than a purely alphabetical scatter.* + *The Config/Fleet numeric prefixes keep the GitHub Actions sidebar in practical execution order rather than a purely alphabetical scatter.* -- **Azure DevOps**: the Pipelines list sorts by the pipeline **definition name** chosen at import time (not by filename). Use the same `Setup: NN` / `Fleet: NN` naming when you import so the list stays in operational order. +- **Azure DevOps**: the Pipelines list sorts by the pipeline **definition name** chosen at import time (not by filename). Use the same `Config: NN` / `Fleet: NN` naming when you import so the list stays in operational order. If you prefer a different naming scheme (e.g. `00 - Auth`, `01 - Inventory`, ...), just change the `name:` field in each GH Actions YAML and / or pick a different prefix at ADO import time. Nothing else in the module depends on these display names. @@ -499,7 +499,17 @@ az role assignment create ` **Step 3 - federate the workflow** -> **Plan your GitHub environments now**: environment-scoped subjects (`...:environment:`) only succeed at workflow run time if a GitHub environment with the exact same name exists in the repo (names are **case-sensitive**). The `az` command will accept any string you put in `subject` - Entra ID does **not** validate it against GitHub - but a missing or mistyped environment fails the OIDC exchange at runtime with `AADSTS70021: No matching federated identity record found`. The create order does not technically matter, but it is easiest to decide on environment names now (and ideally create them up-front under **your repo -> Settings -> Environments -> New environment**) so the strings you put into the federated credentials definitely match what GitHub will later send in the token. For the ring-based rollout pattern this guide describes, three are recommended: +> **Are GitHub environments required? No - they are optional.** The pipelines authenticate to Azure with a **branch-scoped** federated credential (`...:ref:refs/heads/main`, the first `az ad app federated-credential create` block below). That single credential is all OIDC needs - leave the `environment` input **blank** (as in the screenshot most operators see) and every workflow runs under the branch-scoped subject claim. The `environment:` line in each job evaluates to an empty string, GitHub attaches no environment, and `azure/login` exchanges the branch-scoped token. This is the correct, fully-supported minimal setup. +> +> **What environments add (and when to bother).** A GitHub environment is a *governance* wrapper, **not** an OIDC requirement. Create them only when you want one or more of: +> - **Required reviewers / manual approval gates** - e.g. a human must approve before the `apply-updates` job runs against the `Production` ring. +> - **Deployment-branch restrictions** - only allow the workflow to target an environment from `main`. +> - **Wait timers** - enforce a soak period between rings. +> - **Per-environment secrets / variables** - e.g. a *different* `AZURE_CLIENT_ID` (a separate App Registration) per ring, so the pilot ring and the production ring use distinct identities with distinct RBAC scopes. +> +> If you do **not** need any of those, you can skip the environment-scoped credentials, the environment table, and the `environment` input entirely - the branch-scoped credential covers all runs. You can also add environments later without re-doing anything: create the environment, add its environment-scoped federated credential, and start passing its name in the `environment` input. **Is OIDC the reason?** Yes - the only auth-level effect of naming an environment is that GitHub puts `environment:` into the token's `subject` claim instead of `ref:refs/heads/main`, which is why a *named* run needs a matching **environment-scoped** federated credential (the loop block further below). A *blank* run never needs one. +> +> **Plan your GitHub environments now** (only if you decided you want them above): environment-scoped subjects (`...:environment:`) only succeed at workflow run time if a GitHub environment with the exact same name exists in the repo (names are **case-sensitive**). The `az` command will accept any string you put in `subject` - Entra ID does **not** validate it against GitHub - but a missing or mistyped environment fails the OIDC exchange at runtime with `AADSTS70021: No matching federated identity record found`. The create order does not technically matter, but it is easiest to decide on environment names now (and ideally create them up-front under **your repo -> Settings -> Environments -> New environment**) so the strings you put into the federated credentials definitely match what GitHub will later send in the token. **This whole table is optional** - it only applies if you opted into environments above. For the ring-based rollout pattern this guide describes, three are *suggested* (none are required): > > | Environment | Purpose | Suggested protection rules | > |---|---|---| @@ -514,7 +524,10 @@ az role assignment create ` > **GitHub environments and `UpdateRing` tag values are independent.** The `UpdateRing` tag lives on the cluster ARM resource and is what the PowerShell functions filter on (`-UpdateRing Wave1`). A GitHub environment is just an approval gate and federated credential subject. They do **not** have to share names, and the mapping is many-to-many: one GitHub environment can run updates across multiple `UpdateRing` values (different workflow runs pass different `-UpdateRing` parameters under the same approval gate), and multiple environments can target the same `UpdateRing` (e.g. a `PreProductionDryRun` environment that runs with `-WhatIf` against the `Production` ring). The workflow YAML decides which ring tag a given environment-gated run applies to. ```bash -# Branch-scoped credential (for default-branch / scheduled runs). +# REQUIRED - branch-scoped credential (for default-branch / scheduled runs). +# This single credential is enough to run every pipeline with the `environment` +# input left blank. If you are not using GitHub environments, this is the ONLY +# federated credential you need - skip the environment-scoped block below. az ad app federated-credential create ` --id ` --parameters '{ @@ -524,8 +537,10 @@ az ad app federated-credential create ` "audiences": ["api://AzureADTokenExchange"] }' -# Environment-scoped credential - one per GitHub environment (DevTest, PreProduction, Production). -# Repeat this command three times, substituting both `name` and `subject` to match each environment. +# OPTIONAL - environment-scoped credential, one per GitHub environment you chose +# to create (e.g. DevTest, PreProduction, Production). Only needed if you pass an +# environment name in the workflow `environment` input. Skip entirely otherwise. +# Repeat this command once per environment, substituting both `name` and `subject`. az ad app federated-credential create ` --id ` --parameters '{ @@ -542,7 +557,7 @@ Subject-claim patterns for other trigger types: |---|---| | Push to a branch | `repo:/:ref:refs/heads/` | | Pull request | `repo:/:pull_request` | -| Environment | `repo:/:environment:` | +| Environment *(optional)* | `repo:/:environment:` | | Tag | `repo:/:ref:refs/tags/` | > **PowerShell on Windows**: passing the `--parameters` JSON as an inline string (as shown above) fails on Windows PowerShell - and on PowerShell 7+ on Windows - with `Failed to parse string as JSON: ... Expecting property name enclosed in double quotes`. The `az` CLI on Windows is a `.cmd` shim, and cmd.exe strips the inner double quotes from the JSON before `az` ever sees them. Microsoft's [quoting guidance](https://learn.microsoft.com/cli/azure/use-azure-cli-successfully-quoting#json-strings) recommends bypassing the shell entirely by writing the JSON to a file and passing it with the `@` prefix - this is the universally safe pattern and works on Linux/macOS too: @@ -563,8 +578,9 @@ Subject-claim patterns for other trigger types: > --id ` > --parameters "@$paramsFile" > -> # Environment-scoped credentials - one per GitHub environment (names are case-sensitive -> # and must match the environments that will exist in your repo at workflow run time) +> # OPTIONAL - environment-scoped credentials, one per GitHub environment (names are +> # case-sensitive and must match the environments in your repo at workflow run time). +> # Skip this foreach entirely if you are running with the `environment` input blank. > foreach ($envName in 'DevTest','PreProduction','Production') { > @{ > name = "GitHubActions-$envName" @@ -628,7 +644,7 @@ You can add the secrets via the **GitHub UI** (**Settings -> Secrets and variabl > > `gh` reuses the credentials of the signed-in account, so it can write secrets to any repo that account can write to. No personal access token needed for interactive use. -**Script the secrets and environments (recommended)** - end-to-end: creates the GitHub environments your federated credentials reference, writes the three repo-level secrets, and (optionally) pins `AZURE_CLIENT_ID` at each environment. Substitute `/` for your target repo: +**Script the secrets and environments** - end-to-end: creates the GitHub environments your federated credentials reference (**optional** - only needed if you opted into environments in step 3; if you are running with the `environment` input blank, you can skip the environment-creation loop and keep just the secret/variable writes), writes the three repo-level secrets, and (optionally) pins `AZURE_CLIENT_ID` at each environment. Substitute `/` for your target repo: ```powershell # Inputs - reuse the variables from the federation step where you can @@ -940,13 +956,13 @@ Both platforms expect the YAML files inside this folder to land in a platform-sp > Copy-AzLocalPipelineExample -Destination .\pipelines -Platform AzureDevOps > ``` > -> The function prints a short "next steps" summary pointing at the copied YAML location with the recommended workflow / pipeline to run first (GitHub: **Setup: 01 - Validate Auth and Inventory Clusters**, Azure DevOps: auth validation + inventory onboarding). Supports `-Platform GitHub | AzureDevOps | All`, `-PassThru`, `-WhatIf`, `-Confirm`. +> The function prints a short "next steps" summary pointing at the copied YAML location with the recommended workflow / pipeline to run first (GitHub: **Config: 01 - Validate Auth and Inventory Clusters**, Azure DevOps: auth validation + inventory onboarding). Supports `-Platform GitHub | AzureDevOps | All`, `-PassThru`, `-WhatIf`, `-Confirm`. > > **Refusing to overwrite**: the function will refuse to overwrite any file that already exists in `-Destination`, listing the conflicts in the error message. To refresh after a module upgrade, delete the existing copies first (`Remove-Item .\.github\workflows\*.yml`) and re-run. ### 5.1 GitHub Actions -1. **Run Setup: 01 first (strongly recommended).** Before exercising the Fleet workflows, validate that the App Registration, federated credentials, GitHub secrets, environments, and RBAC role assignment all line up - and capture the count + per-subscription detail of subscriptions visible to the pipeline identity - by running **`Setup: 01 - Validate Auth and Inventory Clusters`**. This narrows any failure to one onboarding workflow instead of debugging multiple operational workflows simultaneously. **Re-run periodically** (recommended monthly, or after any RBAC change in the tenant) to confirm the pipeline identity's subscription scope has not silently widened or narrowed. +1. **Run Config: 01 first (strongly recommended).** Before exercising the Fleet workflows, validate that the App Registration, federated credentials, GitHub secrets, environments, and RBAC role assignment all line up - and capture the count + per-subscription detail of subscriptions visible to the pipeline identity - by running **`Config: 01 - Validate Auth and Inventory Clusters`**. This narrows any failure to one onboarding workflow instead of debugging multiple operational workflows simultaneously. **Re-run periodically** (recommended monthly, or after any RBAC change in the tenant) to confirm the pipeline identity's subscription scope has not silently widened or narrowed. The onboarding workflow ships with the module at [`github-actions/setup-validate-and-inventory.yml`](./github-actions/setup-validate-and-inventory.yml). It emits the auth validation JUnit report (Authentication / Subscription Scope / Resource Graph Reachability), writes a setup-focused markdown summary (including a collapsible subscription-details section), and exports both auth and inventory artifacts for downstream workflows. @@ -971,8 +987,8 @@ Both platforms expect the YAML files inside this folder to land in a platform-sp At the `gh` CLI level, `gh run watch` shows the run summary as the steps complete (`gh` actually renders these as Unicode check marks; reproduced here in ASCII): ```text - ? Select a workflow run * Setup: 01 - Validate Auth and Inventory Clusters, Setup: 01 - Validate Auth and Inventory Clusters [main] 12s ago - [OK] main Setup: 01 - Validate Auth and Inventory Clusters - + ? Select a workflow run * Config: 01 - Validate Auth and Inventory Clusters, Config: 01 - Validate Auth and Inventory Clusters [main] 12s ago + [OK] main Config: 01 - Validate Auth and Inventory Clusters - Triggered via workflow_dispatch less than a minute ago JOBS @@ -985,7 +1001,7 @@ Both platforms expect the YAML files inside this folder to land in a platform-sp [OK] Post Azure login (OIDC) [OK] Complete job - [OK] Run Setup: 01 - Validate Auth and Inventory Clusters () completed with 'success' + [OK] Run Config: 01 - Validate Auth and Inventory Clusters () completed with 'success' ``` Inside the `Collect Authentication and Subscription Scope Report` step, the run log shows: @@ -1006,7 +1022,7 @@ Both platforms expect the YAML files inside this folder to land in a platform-sp ``` - ![Setup: 01 - Validate Auth and Inventory Clusters run, showing authentication validation output and subscription scope details](../docs/images/auth-smoke-test-validate-oidc.png) + ![Config: 01 - Validate Auth and Inventory Clusters run, showing authentication validation output and subscription scope details](../docs/images/auth-smoke-test-validate-oidc.png) You may see one informational `windows-latest` -> `windows-2025-vs2026` migration notice in the run annotations. The sample workflows pin `runs-on: windows-latest` (the module is a Windows-side PowerShell module), and GitHub will retarget the alias to the new image automatically when it becomes the default - no action required on your part. As of v0.7.60 the previously-seen Node.js 20 deprecation banner (against `actions/checkout@v4`, `azure/login@v2`, `actions/upload-artifact@v4`, `dorny/test-reporter@v1`) is gone: the sample workflows have been refreshed to Node 24-compatible majors (`@v5`, `@v3`, `@v6`, `@v3` respectively). @@ -2018,9 +2034,9 @@ Automation-Pipeline-Examples/ templates/ incident-body.md # - Mustache-style ticket body template. github-actions/ - setup-validate-and-inventory.yml # Setup: 01. Auth + subscription-scope validation and cluster inventory (merged auth+inventory in v0.8.85; manual + weekly Sun 08:00 UTC). - manage-updatering-tags.yml # Setup: 02. Apply UpdateRing / UpdateStartWindow / UpdateExclusionsWindow / UpdateExcluded tags (manual). - apply-updates-schedule-audit.yml # Setup: 03. Weekly read-only audit: UpdateStartWindow tags vs apply-updates cron (Mon 05:00 UTC, v0.7.65). + setup-validate-and-inventory.yml # Config: 01. Auth + subscription-scope validation and cluster inventory (merged auth+inventory in v0.8.85; manual + weekly Sun 08:00 UTC). + manage-updatering-tags.yml # Config: 02. Apply UpdateRing / UpdateStartWindow / UpdateExclusionsWindow / UpdateExcluded tags (manual). + apply-updates-schedule-audit.yml # Config: 03. Weekly read-only audit: UpdateStartWindow tags vs apply-updates cron (Mon 05:00 UTC, v0.7.65). assess-update-readiness.yml # Fleet: 01. Pre-flight readiness report (manual; v0.7.0). fleet-connectivity-status.yml # Fleet: 02. Daily fleet connectivity / Arc / NIC / Resource Bridge snapshot + node-coverage reconciliation (daily 05:30 UTC, v0.7.79+; reconciliation enhanced in v0.7.85). sideload-updates.yml # Fleet: 03. Opt-in self-hosted-runner workflow: Robocopy + WinRM sideload of solution-update media to clusters gated on UpdateSideloaded (manual; v0.8.7). @@ -2045,7 +2061,7 @@ Automation-Pipeline-Examples/ ## 14. Pipeline reference -Moved to [docs/appendix-pipelines.md](docs/appendix-pipelines.md) - one section per pipeline (`Setup: 01 - ...` ... `Fleet: 07 - ...`) mapping 1:1 to the bundled `*.yml` workflows, with purpose, inputs, trigger, cmdlets invoked, dependencies, artefacts, RBAC, and exit conditions for each. Kept out-of-line to keep this README focused on the runbook. +Moved to [docs/appendix-pipelines.md](docs/appendix-pipelines.md) - one section per pipeline (`Config: 01 - ...` ... `Fleet: 07 - ...`) mapping 1:1 to the bundled `*.yml` workflows, with purpose, inputs, trigger, cmdlets invoked, dependencies, artefacts, RBAC, and exit conditions for each. Kept out-of-line to keep this README focused on the runbook. ## Appendix B: Release history 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 e235ec60..f3cae97b 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 @@ -1,5 +1,5 @@ # AZLOCAL-PIPELINE-ID: apply-updates-schedule-audit -# Setup: 03 - Apply-Updates Schedule Coverage Audit Pipeline +# Config: 03 - 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. @@ -87,7 +87,7 @@ parameters: default: false variables: - GENERATED_AGAINST_MODULE_VERSION: '0.8.85' + GENERATED_AGAINST_MODULE_VERSION: '0.8.86' REQUIRED_MODULE_VERSION: '${{ parameters.moduleVersion }}' reportsPath: '$(Build.ArtifactStagingDirectory)/reports' # v0.8.7 sideload advisor defaults. Override at the pipeline / variable-group @@ -98,7 +98,7 @@ variables: stages: - stage: ScheduleCoverage - displayName: 'Setup: 03 - Audit Apply-Updates Schedule Coverage' + displayName: 'Config: 03 - Audit Apply-Updates Schedule Coverage' jobs: - job: Audit displayName: 'Run Schedule Coverage Advisor' 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 5d4558ca..2ecb5cea 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.85' + value: '0.8.86' # 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 aaedbcb0..d4458972 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.85' + GENERATED_AGAINST_MODULE_VERSION: '0.8.86' # 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 9c4fe23d..60fabc7c 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.85' + GENERATED_AGAINST_MODULE_VERSION: '0.8.86' # 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 2e32cc7c..8e85f945 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.85' + GENERATED_AGAINST_MODULE_VERSION: '0.8.86' # 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 b05f415d..efb2b9e1 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.85' + GENERATED_AGAINST_MODULE_VERSION: '0.8.86' # 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 b095dac9..26140ab6 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 @@ -1,5 +1,5 @@ # AZLOCAL-PIPELINE-ID: manage-updatering-tags -# Setup: 02 - Manage UpdateRing Tags +# Config: 02 - Manage UpdateRing Tags # This pipeline creates or updates UpdateRing tags on Azure Local clusters from a CSV file # # WORKFLOW: @@ -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.85' + value: '0.8.86' # 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). @@ -58,7 +58,7 @@ pool: stages: - stage: ManageTags - displayName: 'Setup: 02 - Manage UpdateRing Tags' + displayName: 'Config: 02 - Manage UpdateRing Tags' jobs: - job: ApplyTags displayName: 'Apply UpdateRing Tags' 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 015aa066..2005dcc4 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.85' + GENERATED_AGAINST_MODULE_VERSION: '0.8.86' REQUIRED_MODULE_VERSION: '${{ parameters.moduleVersion }}' reportsPath: '$(Build.ArtifactStagingDirectory)/reports' diff --git a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/setup-validate-and-inventory.yml b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/setup-validate-and-inventory.yml index e45dc66d..05944b11 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/setup-validate-and-inventory.yml +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/azure-devops/setup-validate-and-inventory.yml @@ -1,5 +1,5 @@ # AZLOCAL-PIPELINE-ID: setup-validate-and-inventory -# Setup: 01 - Validate Auth and Inventory Azure Local Clusters +# Config: 01 - Validate Auth and Inventory Azure Local Clusters # # PURPOSE: # This is the first step in the setup workflow. It combines two operations: @@ -41,11 +41,11 @@ # cluster-inventory (ClusterUpdateRings.csv + .json + README_Instructions.txt). # # HOW TO RUN (after the file is committed and the pipeline is imported): -# Pipelines -> Setup: 01 - Validate Auth and Inventory Clusters -> Run pipeline. +# Pipelines -> Config: 01 - Validate Auth and Inventory Clusters -> Run pipeline. # # See Automation-Pipeline-Examples/README.md, section 5, for the full setup story. -name: 'Setup: 01 - Validate Auth and Inventory Clusters' +name: 'Config: 01 - Validate Auth and Inventory Clusters' # BEGIN-AZLOCAL-CUSTOMIZE:schedule-triggers # Add or modify `schedules:` blocks here to enable cron-driven runs. Content @@ -79,7 +79,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.85' + value: '0.8.86' # 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). @@ -155,7 +155,7 @@ stages: inputs: testResultsFormat: 'JUnit' testResultsFiles: '$(Build.ArtifactStagingDirectory)/auth-report/auth-report.xml' - testRunTitle: '[JUnit Debug] Setup: 01 - Authentication Validation' + testRunTitle: '[JUnit Debug] Config: 01 - Authentication Validation' failTaskOnFailedTests: false mergeTestResults: false 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 06b44210..2975fb5b 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.85' + GENERATED_AGAINST_MODULE_VERSION: '0.8.86' # 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/docs/appendix-pipelines.md b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/docs/appendix-pipelines.md index f10e75d8..2270ece2 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/docs/appendix-pipelines.md +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/docs/appendix-pipelines.md @@ -2,19 +2,19 @@ > **What you will find here:** A comprehensive reference index for every bundled workflow (GitHub Actions + Azure DevOps twins), with a one-line summary of the job, its triggers, the cmdlets it invokes, what it depends on, and the artefacts it produces. Use this as the at-a-glance reference card after you have read the per-workflow runbook in the main pipeline [README.md](../README.md). -Each pipeline section heading below uses **`Setup: NN`** or **`Fleet: NN`** naming to match the operator-facing workflow names. These identifiers appear in the workflow `name:` fields (GH Actions), ADO pipeline import names, the parent README's section 1.1 table, and these section headings. The execution order in the [end-to-end runbook](../README.md#6-end-to-end-runbook-bring-an-estate-online) is operational - read it for the recommended sequence. +Each pipeline section heading below uses **`Config: NN`** or **`Fleet: NN`** naming to match the operator-facing workflow names. These identifiers appear in the workflow `name:` fields (GH Actions), ADO pipeline import names, the parent README's section 1.1 table, and these section headings. The execution order in the [end-to-end runbook](../README.md#6-end-to-end-runbook-bring-an-estate-online) is operational - read it for the recommended sequence. --- ## Default triggers and schedules (at a glance) -The table below is the ground truth for what each shipped YAML does **out of the box**. Four of the ten pipelines (`fleet-connectivity-status`, `fleet-update-status`, `fleet-health-status`, `apply-updates-schedule-audit`) are pre-wired with `schedule:` (GH) / `schedules:` (ADO) blocks. The remaining six (all Setup workflows + `assess-update-readiness`, `apply-updates`, `monitor-updates`) are manual-only by design (`monitor-updates` ships a commented-out `*/30 * * * *` cron sample for in-wave use). +The table below is the ground truth for what each shipped YAML does **out of the box**. Four of the ten pipelines (`fleet-connectivity-status`, `fleet-update-status`, `fleet-health-status`, `apply-updates-schedule-audit`) are pre-wired with `schedule:` (GH) / `schedules:` (ADO) blocks. The remaining six (all Config workflows + `assess-update-readiness`, `apply-updates`, `monitor-updates`) are manual-only by design (`monitor-updates` ships a commented-out `*/30 * * * *` cron sample for in-wave use). | Pipeline | GitHub Actions trigger | Azure DevOps trigger | Notes | |---|---|---|---| -| **Setup: 01** - Validate Auth and Inventory Clusters | `workflow_dispatch` + weekly `schedule: cron '0 8 * * 0'` (Sun 08:00 UTC) | `trigger: none` + weekly `schedules: cron '0 8 * * 0'` (Sun 08:00 UTC) | Run on initial wiring + after every RBAC / federated-credential / subscription change. Ships a weekly Sunday 08:00 UTC re-validation + inventory refresh; edit or remove the cron in the `BEGIN/END-AZLOCAL-CUSTOMIZE:schedule-triggers` block to change the cadence. | -| **Setup: 02** - Manage UpdateRing Tags | `workflow_dispatch` only | `trigger: none` (manual only) | Runs on-demand whenever you edit the CSV. | -| **Setup: 03** - Apply-Updates Schedule Coverage Audit | `workflow_dispatch` + `schedule: cron '0 5 * * 1'` (Mondays 05:00 UTC) | `trigger: none` + `schedules: cron '0 5 * * 1'` | Weekly read-only drift advisor: compares apply-updates cron(s) to `UpdateStartWindow` tags. Runs before the daily fleet pipelines so its annotations are first on Monday mornings. | +| **Config: 01** - Validate Auth and Inventory Clusters | `workflow_dispatch` + weekly `schedule: cron '0 8 * * 0'` (Sun 08:00 UTC) | `trigger: none` + weekly `schedules: cron '0 8 * * 0'` (Sun 08:00 UTC) | Run on initial wiring + after every RBAC / federated-credential / subscription change. Ships a weekly Sunday 08:00 UTC re-validation + inventory refresh; edit or remove the cron in the `BEGIN/END-AZLOCAL-CUSTOMIZE:schedule-triggers` block to change the cadence. | +| **Config: 02** - Manage UpdateRing Tags | `workflow_dispatch` only | `trigger: none` (manual only) | Runs on-demand whenever you edit the CSV. | +| **Config: 03** - Apply-Updates Schedule Coverage Audit | `workflow_dispatch` + `schedule: cron '0 5 * * 1'` (Mondays 05:00 UTC) | `trigger: none` + `schedules: cron '0 5 * * 1'` | Weekly read-only drift advisor: compares apply-updates cron(s) to `UpdateStartWindow` tags. Runs before the daily fleet pipelines so its annotations are first on Monday mornings. | | **Fleet: 01** - Assess Update Readiness | `workflow_dispatch` only | `trigger: none` (manual only) | **Recommended customisation** - schedule per ring 12-72h before the matching **Fleet: 04** `UpdateStartWindow` (see [Fleet: 01 - Assess Update Readiness](#fleet-01---assess-update-readiness) below for the per-ring pattern + lead-time table). Always-green at pipeline level; per-cluster readiness gaps surface as JUnit `` entries. | | **Fleet: 02** - Fleet Connectivity Status | `workflow_dispatch` + `schedule: cron '30 5 * * *'` (daily 05:30 UTC) | `trigger: none` + `schedules: cron '30 5 * * *'` | Daily fleet connectivity / Arc / NIC / Resource Bridge snapshot with bidirectional node-coverage reconciliation. Runs 30 min before **Fleet: 06** so connectivity issues are visible upstream of update reporting. | | **Fleet: 03** - Sideload Updates (Opt-in) | `workflow_dispatch` only | `trigger: none` (manual only) | Optional pre-stage on-prem update media workflow for disconnected environments. | @@ -31,7 +31,7 @@ The table below is the ground truth for what each shipped YAML does **out of the --- -## Setup: 01 - Validate Auth and Inventory Azure Local Clusters +## Config: 01 - Validate Auth and Inventory Azure Local Clusters | Aspect | Value | |---|---| @@ -45,13 +45,13 @@ The table below is the ground truth for what each shipped YAML does **out of the | **RBAC** | Whatever the pipeline identity has - the probe itself is read-only and intentionally surfaces both over- and under-grants. | | **Exit conditions** | Pipeline run is green only when every probe passes. A red run means the identity, its role assignment, or its subscription scope is wrong - downstream pipelines will give nonsense results until this passes. | | **ITSM** | Not supported - this is an identity-only probe with no per-cluster failure surface to dispatch on. | -| **Introduced** | v0.7.70 (separate auth + inventory); v0.8.85 (merged into Setup: 01). | +| **Introduced** | v0.7.70 (separate auth + inventory); v0.8.85 (merged into Config: 01). | > **Note**: v0.8.85 consolidates the separate authentication and inventory pipelines into a single `setup-validate-and-inventory.yml` workflow. --- -## Setup: 02 - Manage UpdateRing Tags +## Config: 02 - Manage UpdateRing Tags | Aspect | Value | |---|---| @@ -59,8 +59,8 @@ The table below is the ground truth for what each shipped YAML does **out of the | **Inputs** | `csv_path` (required). | | **Trigger** | Manual only (`workflow_dispatch` / **Run pipeline** button). No schedule shipped - this is a deliberate change-controlled operation that should follow a CSV edit + review. Add a `schedule:` / `schedules:` block if your CSV is auto-generated and you want periodic re-application. | | **Cmdlets invoked** | `Set-AzLocalClusterUpdateRingTag`. | -| **Depends on** | Setup: 01 produces the input CSV (the operator typically downloads `cluster-inventory.csv`, edits ring / window / exclusion values in a PR, then re-uploads it as the input to this pipeline). | -| **Artefacts** | `UpdateRingTag_YYYYMMDD_HHmmss.csv` (per-cluster CSV log: ClusterName, ResourceGroup, SubscriptionId, ResourceId, Action, PreviousTagValue, NewTagValue, Status, Message) and `UpdateRingTag_Results.json` (the same rows in JSON form, written from `-PassThru`) inside the published log artifact. Since v0.8.0 the pipeline job summary tab also renders a **result breakdown table** (Created / Updated / Already in sync / Skipped / WhatIf / Failed) plus a collapsible **per-cluster details** block, at parity with Setup: 03 / Fleet: 01 / Fleet: 04 / Fleet: 06. | +| **Depends on** | Config: 01 produces the input CSV (the operator typically downloads `cluster-inventory.csv`, edits ring / window / exclusion values in a PR, then re-uploads it as the input to this pipeline). | +| **Artefacts** | `UpdateRingTag_YYYYMMDD_HHmmss.csv` (per-cluster CSV log: ClusterName, ResourceGroup, SubscriptionId, ResourceId, Action, PreviousTagValue, NewTagValue, Status, Message) and `UpdateRingTag_Results.json` (the same rows in JSON form, written from `-PassThru`) inside the published log artifact. Since v0.8.0 the pipeline job summary tab also renders a **result breakdown table** (Created / Updated / Already in sync / Skipped / WhatIf / Failed) plus a collapsible **per-cluster details** block, at parity with Config: 03 / Fleet: 01 / Fleet: 04 / Fleet: 06. | | **When to run** | After editing the inventory CSV; whenever ring membership or maintenance windows change. | | **RBAC** | Write to tags only. The built-in **Tag Contributor** role on the cluster scope is sufficient (`Microsoft.Resources/tags/*`). Since v0.7.65, `Set-AzLocalClusterUpdateRingTag` writes via `Microsoft.Resources/tags/default` PATCH, so the broader `Microsoft.AzureStackHCI/clusters/write` is not required. | | **Exit conditions** | Pipeline run is green when every CSV row is processed (added / updated / unchanged). Per-cluster tag-write failures surface in the run log; the pipeline does not currently fail on per-row errors. Re-run after triaging is safe (the cmdlet is idempotent). | @@ -68,7 +68,7 @@ The table below is the ground truth for what each shipped YAML does **out of the --- -## Setup: 03 - Apply-Updates Schedule Coverage Audit +## Config: 03 - Apply-Updates Schedule Coverage Audit | Aspect | Value | |---|---| @@ -76,7 +76,7 @@ The table below is the ground truth for what each shipped YAML does **out of the | **Inputs** | `pipeline_path` (file or folder; default `.github/workflows` on GitHub Actions, `.azure-pipelines` on Azure DevOps - the standard consumer locations for the bundled `apply-updates.yml` sample), `lead_time_minutes` (0-60, default 5), `include_untagged` (default false), `module_version` (optional). | | **Trigger** | Manual (`workflow_dispatch` / **Run pipeline** button) **plus** scheduled weekly on Mondays at 05:00 UTC (`cron '0 5 * * 1'`). Deliberately runs before the daily `fleet-connectivity-status` (05:30 UTC), `fleet-update-status` (06:00 UTC), and `fleet-health-status` (07:00 UTC) pipelines so its drift annotations land at the top of the operator's Monday-morning inbox. Edit the cron in the YAML to change cadence. | | **Cmdlets invoked** | `Test-AzLocalApplyUpdatesScheduleCoverage`, `Get-AzLocalApplyUpdatesScheduleConfig` (when a `schedule.yml` is present in the repo). | -| **Depends on** | Setup: 01 (cluster inventory + `UpdateStartWindow` tags), Setup: 02 (tags applied) for non-trivial output. Runs fine on an empty fleet but the audit is meaningless. | +| **Depends on** | Config: 01 (cluster inventory + `UpdateStartWindow` tags), Config: 02 (tags applied) for non-trivial output. Runs fine on an empty fleet but the audit is meaningless. | | **Artefacts** | `schedule-coverage-audit.xml` (JUnit, one `` per `(UpdateRing, UpdateStartWindow)` pair, uncovered = ``), `schedule-coverage-audit.csv` (full Audit view with `Status` / `Recommendation` columns), `schedule-coverage-matrix.csv` (every distinct `(Ring, Window)` pair with its required cron), `schedule-coverage-recommend.md` (ready-to-paste GH Actions + Azure DevOps cron blocks), markdown step summary. | | **When to run** | Hands-off scheduled. Trigger manually whenever you have just tagged a new ring or changed a maintenance window - see the [end-to-end runbook in section 8.3](../README.md#83-end-to-end-runbook-apply-updates-schedule-coverage-audit). | | **RBAC** | Read-only - `Reader` on the cluster scope plus `Microsoft.ResourceGraph/resources/read`. No write actions are ever taken. Covered by the `Azure Stack HCI Update Operator (custom)` custom role. | @@ -94,7 +94,7 @@ The table below is the ground truth for what each shipped YAML does **out of the | **Inputs** | `subscription_ids` (optional - comma-separated; defaults to every subscription the federated identity can read), `module_version` (optional). v0.7.4-style ITSM toggles are also exposed: `raise_itsm_ticket`, `itsm_config_path`, `itsm_dry_run`, `itsm_force_create` (all optional, default off). | | **Trigger** | Manual (`workflow_dispatch` / **Run pipeline** button) **plus** scheduled daily at 05:30 UTC (`cron '30 5 * * *'`). Deliberately runs 30 minutes before `fleet-update-status` (06:00 UTC) so connectivity issues are visible upstream of update reporting. Edit the cron in the YAML to change cadence. | | **Cmdlets invoked** | `Get-AzLocalFleetConnectivityStatus`. When ITSM is enabled: `Get-AzLocalItsmConfig`. | -| **Depends on** | None directly. Setup: 01 should be green at least once so the subscription scope is trusted. This is the upstream "can we see the fleet at all?" probe that other fleet pipelines silently rely on. | +| **Depends on** | None directly. Config: 01 should be green at least once so the subscription scope is trusted. This is the upstream "can we see the fleet at all?" probe that other fleet pipelines silently rely on. | | **Artefacts** | `fleet-connectivity-status.xml` (JUnit, one `` per cluster), `fleet-cluster-connectivity.csv` (per-cluster reconciliation), `fleet-arc-status-summary.csv` (per-cluster Arc agent counts), `fleet-arc-non-connected-machines.csv` (per-machine triage list), `fleet-physical-nics.csv` (NIC issues only), `fleet-physical-nic-all.csv` (full NIC inventory), `fleet-physical-nic-stats.csv` (histogram by `NicType` + `NicStatus`), `fleet-arb-status.csv` (Azure Resource Bridge state), markdown job summary including the *"How to interpret + act on a non-zero reconciliation"* guidance subsection. | | **When to run** | Hands-off scheduled. Trigger manually whenever the apply-updates chain returns "zero ready clusters" or the fleet count drops unexpectedly - Fleet: 02 is the upstream "can we see the fleet at all?" probe. | | **RBAC** | Read-only - `Reader` plus `Microsoft.ResourceGraph/resources/read`, `Microsoft.AzureStackHCI/edgeDevices/read`, `Microsoft.HybridCompute/machines/read`, and `Microsoft.ResourceConnector/appliances/read`. All four already live in the **`Azure Stack HCI Update Operator (custom)`** custom role definition shipped in [section 3.1](../README.md#31-custom-role-azure-stack-hci-update-operator-custom). | @@ -116,7 +116,7 @@ The table below is the ground truth for what each shipped YAML does **out of the | **Inputs** | `update_ring` (required), `throttle_limit` (optional). | | **Trigger** | **Manual only by default** (`workflow_dispatch` / **Run pipeline** button). **No schedule is shipped** because the `update_ring` input is required and there is no single ring value that would be correct for every consumer. See the **recommended customisation note below** for the per-ring scheduling pattern + lead-time guidance. | | **Cmdlets invoked** | `Get-AzLocalClusterInventory`, `Get-AzLocalClusterUpdateReadiness`, `Test-AzLocalClusterHealth`. | -| **Depends on** | Setup: 01 (an `UpdateRing` tag must exist on at least one cluster to scope the readiness query) and Setup: 02 (to apply that tag at scale). | +| **Depends on** | Config: 01 (an `UpdateRing` tag must exist on at least one cluster to scope the readiness query) and Config: 02 (to apply that tag at scale). | | **Artefacts** | `readiness.xml`, `readiness.csv`, `health-blocking.xml`, `health-blocking.csv`. | | **When to run** | 12-72 hours before each Fleet: 04 wave for the same ring (12-24h for small rings, 48-72h for large rings of 50+ clusters) - long enough for an operator to triage any `` entries before the maintenance window opens, short enough that the readiness signal is still fresh. See the **recommended customisation note below** for the cron pattern. | | **RBAC** | Read-only. Covered by the `Azure Stack HCI Update Operator (custom)` custom role. | @@ -133,7 +133,7 @@ The table below is the ground truth for what each shipped YAML does **out of the > | Medium (10-50 clusters) | 24-48 hours ahead | `'0 2 * * 4'` (Thu 02:00 ahead of a Sat 02:00 Fleet: 04) | Lets you raise tickets / engage cluster owners on per-cluster issues before the weekend. | > | Large (50+ clusters) | 48-72 hours ahead | `'0 2 * * 3'` (Wed 02:00 ahead of a Sat 02:00 Fleet: 04) | Allows time to escalate, swap clusters into a deferral ring (`UpdateExcluded=True`), or stage parallel mitigation. | > -> **Known gap**: the Setup: 03 schedule-coverage audit currently validates Fleet: 04 cron-to-`UpdateStartWindow` coverage only - it does **not** audit whether each Fleet: 01 cron is correctly anchored to a Fleet: 04 cron. Pair Fleet: 01 and Fleet: 04 cron edits in the same PR so the lead-time relationship is reviewable. The end-to-end runbook in the parent README's [section 8.1.1](../README.md#811-recommended-step5-pre-flight-schedule-per-ring) has worked examples for the most common ring layouts. +> **Known gap**: the Config: 03 schedule-coverage audit currently validates Fleet: 04 cron-to-`UpdateStartWindow` coverage only - it does **not** audit whether each Fleet: 01 cron is correctly anchored to a Fleet: 04 cron. Pair Fleet: 01 and Fleet: 04 cron edits in the same PR so the lead-time relationship is reviewable. The end-to-end runbook in the parent README's [section 8.1.1](../README.md#811-recommended-step5-pre-flight-schedule-per-ring) has worked examples for the most common ring layouts. ## Fleet: 03 - Sideload Updates (Opt-in) @@ -148,7 +148,7 @@ The table below is the ground truth for what each shipped YAML does **out of the | **Trigger** | **Manual only by default** (`workflow_dispatch` / **Run pipeline** button). **No schedule ships** because the pipeline requires an on-prem self-hosted runner labelled `azlocal-sideload` (GH) / a self-hosted agent in a pool with the `azlocal-sideload` demand (ADO) that most repos do not have. Once the runner/agent is online, uncomment the bundled `*/30 * * * *` cron inside the `BEGIN/END-AZLOCAL-CUSTOMIZE:schedule-triggers` block to drive the state machine. | | **Cmdlets invoked** | `Resolve-AzLocalSideloadPlan` (selects clusters whose next apply window is within `SIDELOAD_LEAD_DAYS`), `Invoke-AzLocalSideloadUpdate` (the re-entrant state machine), `Export-AzLocalSideloadStatusReport` + `Add-AzLocalSideloadStepSummary` (JUnit + Markdown summary). | | **Re-entrant state machine** | Each run advances every in-scope cluster by **one** transition and exits: `Planned -> Copying -> Copied -> Verified -> Imported -> SideloadFlagged`. The multi-hour copy itself runs in a **detached Windows Scheduled Task** (driven by `Tools/Invoke-AzLocalSideloadCopyTask.ps1`) so no pipeline run is ever long-lived. Re-running is always safe; a stale `Copying` heartbeat (> `SIDELOAD_HEARTBEAT_STALE_MINUTES`) is re-driven. | -| **Depends on** | Setup: 01 (`UpdateRing` + sideload tags present, including `UpdateAuthAccountId`), Setup: 02 (to apply those tags at scale), and a populated `sideload-auth-map.csv` + `sideload-catalog.yml`. The downstream Fleet: 04 apply consumes the `UpdateSideloaded=True` flag this pipeline sets. | +| **Depends on** | Config: 01 (`UpdateRing` + sideload tags present, including `UpdateAuthAccountId`), Config: 02 (to apply those tags at scale), and a populated `sideload-auth-map.csv` + `sideload-catalog.yml`. The downstream Fleet: 04 apply consumes the `UpdateSideloaded=True` flag this pipeline sets. | | **Artefacts** | `sideload-status.xml` (JUnit, one cluster per test), `sideload-status.csv`, plus per-run copy/verify logs under the shared `SIDELOAD_STATE_ROOT\logs`. | | **Shared state** | `SIDELOAD_STATE_ROOT` must be a UNC path that every runner/agent can read+write (`state\`, `logs\`, `cache\`). Verified media is cached under `SIDELOAD_CACHE_ROOT` (defaults to `\cache`) so a bundle is downloaded + hashed once and reused across clusters. | | **RBAC** | ARG fleet read + Key Vault secret read + `UpdateSideloaded` tag write for the pipeline identity; a separate Active Directory `[pscredential]` (built from two Key Vault secrets named in the auth-map row) is used for the cluster WinRM remoting and `Add-SolutionUpdate`. | @@ -173,7 +173,7 @@ The table below is the ground truth for what each shipped YAML does **out of the | **Inputs** | `update_ring` (required), `update_name` (optional - leave blank for latest), `dry_run` (optional), `throttle_limit` (optional). **v0.7.4 adds** `raise_itsm_ticket`, `itsm_config_path`, `itsm_dry_run`, `itsm_force_create` (all optional, defaults preserve existing behaviour). | | **Trigger** | **Manual only by default** (`workflow_dispatch` / **Run pipeline** button). **No schedule is shipped** - you must add one. See the **mandatory customisation note below** and the schedule-alignment guidance in [section 8](../README.md#8-scheduling-maintenance-windows-and-change-freeze-periods). | | **Cmdlets invoked** | `Get-AzLocalApplyUpdatesScheduleConfig`, `Get-AzLocalClusterUpdateReadiness`, `Start-AzLocalClusterUpdate`. When ITSM is enabled: `Get-AzLocalItsmConfig`. | -| **Depends on** | Setup: 01 (`UpdateRing` tags present), Fleet: 01 (readiness reviewed for the wave). Setup: 03 (schedule coverage audit) should be green so the cron(s) that start this pipeline actually fire at every tagged `UpdateStartWindow`. | +| **Depends on** | Config: 01 (`UpdateRing` tags present), Fleet: 01 (readiness reviewed for the wave). Config: 03 (schedule coverage audit) should be green so the cron(s) that start this pipeline actually fire at every tagged `UpdateStartWindow`. | | **Artefacts** | `update-results.xml` (JUnit, one cluster per test), `update-logs/*` (CSV + detail). When ITSM is enabled: `itsm-results.csv`, `itsm-results.xml`. | | **When to run** | During the maintenance window for each ring, after the Fleet: 01 readiness assessment is reviewed. | | **RBAC** | Write to clusters required. Covered by the `Azure Stack HCI Update Operator (custom)` custom role (`Microsoft.AzureStackHCI/clusters/updates/apply/action` + cluster-update reads). | 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 7cdb7454..6df7771b 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 @@ -31,7 +31,7 @@ # 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: 'Setup: 03 - Apply-Updates Schedule Coverage Audit' +name: 'Config: 03 - Apply-Updates Schedule Coverage Audit' on: # BEGIN-AZLOCAL-CUSTOMIZE:schedule-triggers @@ -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.85' + GENERATED_AGAINST_MODULE_VERSION: '0.8.86' 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 36d408ee..d81ec4c2 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.85' + GENERATED_AGAINST_MODULE_VERSION: '0.8.86' # 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 33f0910d..924a8ca5 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.85' + GENERATED_AGAINST_MODULE_VERSION: '0.8.86' # 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 f14a80f4..0ce0ad0a 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.85' + GENERATED_AGAINST_MODULE_VERSION: '0.8.86' # 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 ea52f0e7..1a988bfe 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.85' + GENERATED_AGAINST_MODULE_VERSION: '0.8.86' # 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 9a17dafe..d267508c 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.85' + GENERATED_AGAINST_MODULE_VERSION: '0.8.86' # 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 b5577735..c9ed17e5 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 @@ -22,7 +22,7 @@ # Workflow name carries the same Setup prefix as the filename so the GitHub # Actions sidebar (which sorts workflows alphabetically by this `name:` field) # lists the setup and operational pipelines in execution order. -name: 'Setup: 02 - Manage UpdateRing Tags' +name: 'Config: 02 - Manage UpdateRing Tags' on: # BEGIN-AZLOCAL-CUSTOMIZE:schedule-triggers @@ -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.85' + GENERATED_AGAINST_MODULE_VERSION: '0.8.86' # 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 546c8f30..087fa286 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.85' + GENERATED_AGAINST_MODULE_VERSION: '0.8.86' # 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/setup-validate-and-inventory.yml b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/setup-validate-and-inventory.yml index cf7deddb..4aa08f3b 100644 --- a/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/setup-validate-and-inventory.yml +++ b/AzLocal.UpdateManagement/Automation-Pipeline-Examples/github-actions/setup-validate-and-inventory.yml @@ -1,5 +1,5 @@ # AZLOCAL-PIPELINE-ID: setup-validate-and-inventory -# Setup: 01 - Validate Auth and Inventory Azure Local Clusters +# Config: 01 - Validate Auth and Inventory Azure Local Clusters # # PURPOSE: # This is the first step in the setup workflow. It combines two operations: @@ -42,7 +42,7 @@ # # See Automation-Pipeline-Examples/README.md for full setup story. -name: 'Setup: 01 - Validate Auth and Inventory Clusters' +name: 'Config: 01 - Validate Auth and Inventory Clusters' on: workflow_dispatch: @@ -73,7 +73,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.85' + GENERATED_AGAINST_MODULE_VERSION: '0.8.86' # 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). @@ -171,7 +171,7 @@ jobs: if: always() uses: dorny/test-reporter@v3 with: - name: '[JUnit Debug] Setup: 01 - Authentication Validation' + name: '[JUnit Debug] Config: 01 - Authentication Validation' path: ./reports/auth-report.xml reporter: java-junit fail-on-error: false 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 aef5becc..b5ee2a81 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.85' + GENERATED_AGAINST_MODULE_VERSION: '0.8.86' # 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 01327d92..3a3b643d 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.85' + ModuleVersion = '0.8.86' # Supported PSEditions CompatiblePSEditions = @('Desktop', 'Core') @@ -317,6 +317,8 @@ # ReleaseNotes of this module ReleaseNotes = @' +## Version 0.8.86 - Patch: renames the three onboarding pipeline templates from `Setup: 0N` to `Config: 0N` so the GitHub Actions sidebar (and the Azure DevOps Pipelines list) sorts the onboarding / configuration workflows ahead of the `Fleet: 0N` operational workflows. Both surfaces sort alphabetically by the workflow `name:` / definition name, and `C` (Config) sorts before `F` (Fleet); the previous `Setup:` prefix sorted AFTER `Fleet:`. Only the operator-facing `name:` (GitHub Actions) / `displayName:` (Azure DevOps) fields change - filenames, `AZLOCAL-PIPELINE-ID` values, aliases, prune logic, and all cmdlet behaviour are unchanged. Bundled pipeline README + appendix docs updated to match. No public API or behavioural change. Export count unchanged (still 60). `GENERATED_AGAINST_MODULE_VERSION` bumped from `'0.8.85'` to `'0.8.86'`. + ## Version 0.8.85 - Patch: introduces Setup/Fleet naming in bundled pipeline templates, adds merged GitHub onboarding workflow `setup-validate-and-inventory.yml`, and updates pipeline refresh tooling with optional deprecated-file pruning guarded by AZLOCAL-PIPELINE-ID verification. No public API or behavioural change. Export count unchanged (still 60). `GENERATED_AGAINST_MODULE_VERSION` bumped from `'0.8.84'` to `'0.8.85'`. ## 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'`. diff --git a/AzLocal.UpdateManagement/AzLocal.UpdateManagement.psm1 b/AzLocal.UpdateManagement/AzLocal.UpdateManagement.psm1 index 9b8ffc56..f593905f 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.85' +$script:ModuleVersion = '0.8.86' $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 de1dac21..a591afa2 100644 --- a/AzLocal.UpdateManagement/CHANGELOG.md +++ b/AzLocal.UpdateManagement/CHANGELOG.md @@ -5,6 +5,36 @@ 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.86] - 2026-06-16 + +Patch release. Renames the three onboarding pipeline templates from `Setup: 0N` +to `Config: 0N` so the GitHub Actions sidebar (and the Azure DevOps Pipelines +list) sorts the onboarding / configuration workflows ahead of the `Fleet: 0N` +operational workflows. Both surfaces sort alphabetically by the workflow `name:` +/ definition name, and `C` (Config) sorts before `F` (Fleet); the previous +`Setup:` prefix sorted *after* `Fleet:`. No public API change. Export count +unchanged (still 60). + +### Changed + +- Renames the operator-facing display names of the three onboarding workflows + from `Setup: 01/02/03` to `Config: 01/02/03` across both GitHub Actions + (`name:`) and Azure DevOps (`displayName:` / `name:`) templates. Filenames, + `AZLOCAL-PIPELINE-ID` values, aliases, and `-PruneDeprecated` logic are + unchanged - this is a display-name-only change. +- Updates the bundled pipeline `README.md` and `docs/appendix-pipelines.md` + (section headings, dependency cross-references, naming rationale) to use the + `Config: NN` naming. +- Clarifies in the bundled pipeline `README.md` (step 3, "federate the + workflow") that **GitHub environments are optional**: the branch-scoped + federated credential is all OIDC requires, so leaving the `environment` input + blank is the supported minimal setup. Environments are a governance wrapper + (approval gates, deployment-branch restrictions, wait timers, per-environment + identities) and only need environment-scoped federated credentials when a run + actually names one. +- `GENERATED_AGAINST_MODULE_VERSION` bumped from `'0.8.85'` to `'0.8.86'` + across all bundled GitHub Actions and Azure DevOps pipeline templates. + ## [0.8.85] - 2026-06-16 Patch release. Introduces Setup/Fleet naming in bundled pipeline templates, diff --git a/AzLocal.UpdateManagement/Private/Get-AzLocalPipelineManifest.ps1 b/AzLocal.UpdateManagement/Private/Get-AzLocalPipelineManifest.ps1 index 60407e5f..5db6430d 100644 --- a/AzLocal.UpdateManagement/Private/Get-AzLocalPipelineManifest.ps1 +++ b/AzLocal.UpdateManagement/Private/Get-AzLocalPipelineManifest.ps1 @@ -82,7 +82,7 @@ function Get-AzLocalPipelineManifest { # # v0.8.85: the two standalone onboarding workflows authentication-test.yml # (DisplayStep 0) and inventory-clusters.yml (DisplayStep 1) were merged - # into a single setup-validate-and-inventory.yml (Setup: 01). Its Aliases + # into a single setup-validate-and-inventory.yml (Config: 01). Its Aliases # list BOTH superseded filenames (and their Step.N_ ancestors) so # Update-AzLocalPipelineExample can rename-match a customer who still has # either old file and carry their schedule CRONs into the merged workflow. diff --git a/AzLocal.UpdateManagement/README.md b/AzLocal.UpdateManagement/README.md index 8d1e2716..66990113 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.85 - [Published in PowerShell Gallery](https://www.powershellgallery.com/packages/AzLocal.UpdateManagement/0.8.85) +**Latest Version:** v0.8.86 - [Published in PowerShell Gallery](https://www.powershellgallery.com/packages/AzLocal.UpdateManagement/0.8.86) > 📢 **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.85](#whats-new-in-v0885) +- [What's New in v0.8.86](#whats-new-in-v0886) - [Files](#files) - [Prerequisites](#prerequisites) - [RBAC Requirements](#rbac-requirements) (summary; full reference in [docs/rbac.md](docs/rbac.md)) @@ -86,24 +86,23 @@ 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.85 +## What's New in v0.8.86 -**Pipeline UX consolidation release.** Introduces Setup/Fleet naming in bundled pipelines, adds the merged GitHub onboarding workflow `setup-validate-and-inventory.yml` (Setup: 01), and updates pipeline refresh tooling to safely handle deprecated workflow cleanup with pipeline-ID verification. Also bumps bundled pipeline `GENERATED_AGAINST_MODULE_VERSION` pins to `0.8.85`. +**Pipeline sidebar-ordering fix.** Renames the three onboarding pipeline templates from `Setup: 0N` to `Config: 0N` so the GitHub Actions sidebar (and the Azure DevOps Pipelines list) lists the onboarding / configuration workflows *ahead of* the `Fleet: 0N` operational workflows. Both surfaces sort alphabetically by the workflow `name:` / definition name, and `C` (Config) sorts before `F` (Fleet) - the previous `Setup:` prefix sorted *after* `Fleet:`. -### Added +### Changed -- Bundled GitHub workflows now surface Setup/Fleet ordering and include `Setup: 01 - Validate Auth and Inventory Clusters`. -- Legacy GitHub onboarding workflows (`authentication-test.yml`, `inventory-clusters.yml`) are superseded by the merged Setup workflow in the bundled set. -- `Copy-AzLocalPipelineExample` and `Update-AzLocalPipelineExample` gain optional `-PruneDeprecated` cleanup for superseded workflow files, with AZLOCAL-PIPELINE-ID verification before removal. +- The three onboarding workflows are renamed `Config: 01 - Validate Auth and Inventory Clusters`, `Config: 02 - Manage UpdateRing Tags`, and `Config: 03 - Apply-Updates Schedule Coverage Audit` (was `Setup: 0N`). Display-name (`name:` / `displayName:`) change only - filenames, `AZLOCAL-PIPELINE-ID` values, aliases, and `-PruneDeprecated` logic are unchanged. +- Bundled pipeline `README.md` + `docs/appendix-pipelines.md` updated to use `Config: NN` naming throughout. ### Notes - **No new exports** (count unchanged at 60). -- **`GENERATED_AGAINST_MODULE_VERSION`** bumped from `0.8.84` to `0.8.85` across bundled pipeline templates. +- **`GENERATED_AGAINST_MODULE_VERSION`** bumped from `0.8.85` to `0.8.86` across 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) for full release details. See [`What's New in v0.8.84`](#whats-new-in-v0884) in the Release History for the previous release. +See [CHANGELOG.md](CHANGELOG.md) for full release details. See [`What's New in v0.8.85`](#whats-new-in-v0885) in the Release History for the previous release. ## Files @@ -582,7 +581,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.84** stay above under [`What's New in v0.8.84`](#whats-new-in-v0884). +The most recent release notes for **v0.8.86** stay above under [`What's New in v0.8.86`](#whats-new-in-v0886). + +### What's New in v0.8.85 + +**Pipeline UX consolidation release.** Introduces Setup/Fleet naming in bundled pipelines, adds the merged GitHub onboarding workflow `setup-validate-and-inventory.yml` (Setup: 01), and updates pipeline refresh tooling to safely handle deprecated workflow cleanup with pipeline-ID verification. Also bumps bundled pipeline `GENERATED_AGAINST_MODULE_VERSION` pins to `0.8.85`. (Superseded in v0.8.86, which renames the `Setup: 0N` workflows to `Config: 0N`.) See [CHANGELOG.md](CHANGELOG.md#0885---2026-06-16) for the full v0.8.85 entry. ### What's New in v0.8.83 diff --git a/AzLocal.UpdateManagement/Tests/AzLocal.UpdateManagement.Tests.ps1 b/AzLocal.UpdateManagement/Tests/AzLocal.UpdateManagement.Tests.ps1 index 83999ed2..69b929e2 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.85' { - $script:ModuleInfo.Version | Should -Be '0.8.85' + It 'Should have version 0.8.86' { + $script:ModuleInfo.Version | Should -Be '0.8.86' } It 'Module version constants are in sync between .psm1 and .psd1' { diff --git a/AzLocal.UpdateManagement/Tests/test-run-timings.csv b/AzLocal.UpdateManagement/Tests/test-run-timings.csv index 0bd034f5..947cb7dd 100644 --- a/AzLocal.UpdateManagement/Tests/test-run-timings.csv +++ b/AzLocal.UpdateManagement/Tests/test-run-timings.csv @@ -36,3 +36,4 @@ TimestampUtc,ModuleVersion,Total,Passed,Failed,Skipped,WallClockSeconds,PesterDu "2026-06-16T10:13:27Z","0.8.85","1295","1256","0","1","49.91","48.62","3.18","Invoke-Tests.ps1","5.1.26100.8730" "2026-06-16T10:25:03Z","0.8.85","1295","38","0","0","617.07","615.99","3.31","Invoke-Tests.ps1","5.1.26100.8730" "2026-06-16T11:05:53Z","0.8.85","1305","1265","1","1","148.49","145.99","15.16","Invoke-Tests.ps1","5.1.26100.8730" +"2026-06-16T11:43:15Z","0.8.86","1305","1266","0","1","77.33","75.24","3.83","Invoke-Tests.ps1","5.1.26100.8730" diff --git a/AzLocal.UpdateManagement/docs/release-history.md b/AzLocal.UpdateManagement/docs/release-history.md index 15cf1f29..dfa3a4e5 100644 --- a/AzLocal.UpdateManagement/docs/release-history.md +++ b/AzLocal.UpdateManagement/docs/release-history.md @@ -4,7 +4,19 @@ > > **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.84 (the current release)**, see the main [README.md](../README.md#whats-new-in-v0884) `What's New in v0.8.84` section. +> **For v0.8.86 (the current release)**, see the main [README.md](../README.md#whats-new-in-v0886) `What's New in v0.8.86` section. + +--- + +### What's New in v0.8.86 + +Patch release. Renames the three onboarding pipeline templates from `Setup: 0N` to `Config: 0N` so the GitHub Actions sidebar (and the Azure DevOps Pipelines list) sorts the onboarding / configuration workflows ahead of the `Fleet: 0N` operational workflows. Both surfaces sort alphabetically by the workflow `name:` / definition name, and `C` (Config) sorts before `F` (Fleet); the previous `Setup:` prefix sorted *after* `Fleet:`. Display-name (`name:` / `displayName:`) change only - filenames, `AZLOCAL-PIPELINE-ID` values, aliases, and `-PruneDeprecated` logic are unchanged. Bundled pipeline README + appendix docs updated to match. No public API change. Export count unchanged (still 60). `GENERATED_AGAINST_MODULE_VERSION` bumped from `0.8.85` to `0.8.86` across all bundled pipeline templates. + +--- + +### What's New in v0.8.85 + +Patch release. Introduces Setup/Fleet naming in bundled pipeline templates, adds the merged GitHub onboarding workflow `setup-validate-and-inventory.yml`, and updates pipeline refresh tooling with optional deprecated-file pruning guarded by AZLOCAL-PIPELINE-ID verification. (Superseded in v0.8.86, which renames the `Setup: 0N` workflows to `Config: 0N`.) No public API change. Export count unchanged (still 60). `GENERATED_AGAINST_MODULE_VERSION` bumped from `0.8.84` to `0.8.85`. ---