Pipelines and Workflows¶
Every deployment runs from an entry pipeline in the configuration repository: Azure DevOps YAML under pipelines/,
or a GitHub Actions workflow under .github/workflows/. Entry pipelines are thin. They pass a few parameters to a
reusable template in the code repository, and that template calls the Xebia.Mcp module. Azure DevOps and GitHub Actions
are kept functionally identical (docs/PIPELINE_SYNCHRONIZATION.md and .github/workflows/COUNTERPARTS.md in the code
repository).
Entry pipelines¶
Entry (ADO pipelines/ and GitHub .github/workflows/) |
Calls template (code repository) | Use it for |
|---|---|---|
hub.yaml |
ADO .azure-devops/pipelines/templates/stage-mcp-hub.yaml, GitHub .github/workflows/stage-mcp-hub.yaml |
Any hub change: firewall, App Gateway, policies, features, tags, management groups |
hub-destroy.yaml |
same, with destroy: true |
Tearing down a hub (canary only in the reference repository) |
spoke.yaml |
stage-mcp-spoke.yaml |
Create or change spokes |
spoke-destroy.yaml |
stage-mcp-spoke.yaml, with destroy: true |
Delete spokes |
infrastructure.yaml |
stage-infrastructure.yaml |
Deploy or update the job runners |
container.yaml |
stage-container.yaml |
Build the mcp container image |
The code repository also has:
update-spn.yaml: stores and rotates the deployment SPN secret in the hub Key Vault;test.yaml: runner stress test;pull-request.yaml(ADO) andci.yml(GitHub): CI for the code itself, see Building new functionality;report-version.yaml(Azure DevOps only, scheduled): reports the pinned code version of customer repositories to the platform status API (scripts/version-watcher);trigger-sync.yaml(GitHub only): syncs the code to customer remotes, see Releasing and Code synchronisation.
Which platform environment does each entry target?
Every entry passes one mcp.json per stage or job. Check this before you run, especially for the spoke pipelines:
spoke-destroy must use the same mcp.json as the spoke run that created the spoke. In the reference
configuration repository, spoke.yaml targets prod while spoke-destroy.yaml targets canary.
Parameters you will change¶
These are template parameters. In the reference configuration repository most of them are hard-coded in the entry YAML. To change one, edit the entry file in a pull request, or expose it as a run-time parameter (see Expose mode and whatIf at run time).
| Parameter | Values | Effect |
|---|---|---|
mode |
Plan, Apply, ApproveModifications, ApproveDestroys |
Plan only plans. Apply plans and applies without stopping. ApproveModifications stops for approval when a plan has any create, update or delete. ApproveDestroys stops only when the plan deletes something. |
whatIf |
true, false |
true: Terraform still plans, but the apply step is skipped. See the spoke exception below. |
approveLayers (hub only) |
list of layer names, default [] |
Limits approvals to these layers. Empty means every layer can require approval. |
destroy |
true, false |
Plans and applies a destroy. Set by the *-destroy entry files. |
spokeGlob (spoke only) |
wildcard pattern, relative to <env-folder>/spokes/ |
Selects the spoke files, e.g. team-a/canvas_app1_dev.json or team-a/*. Case-insensitive. |
environments (spoke only) |
ordered list of spoke environments | Which spoke environments run and in which order. A matched file whose environment is not in the list fails the config test. |
imageRef |
e.g. mcp:latest |
The container image tag the jobs run in. |
Spoke exception for whatIf
The spoke subscription layer is always planned and applied during the Plan step, even with whatIf: true
(Invoke-XebiaSpokePlanAction.ps1). Only the spoke canvas layer respects whatIf. Today the subscription layer only
creates a subscription alias when azureSubscriptionId is empty.
Check before you run
In the reference configuration repository (xms-cloud-platform-config), hub.yaml uses mode: Apply with
whatIf: false, so canary and prod apply without approval. spoke.yaml uses mode: Apply with whatIf: true,
so it only plans the spoke canvas. Decide which behavior you want and change the entry files before you rely on them.
What a hub run does¶
-
Prepare. Runs the unit tests and the config tests (Pester):
- firewall JSON against
src/Xebia.Mcp/Files/json/firewallschema.json, IP group references and priorities; - App Gateway and WAF JSON against their schemas;
- the DNS baseline.
It also builds the region matrix. A failing config test stops the run before Terraform starts. 2. For each layer, in layer order: 1.
<layer>_plan:terraform plan. The plan file and a JSON summary are published as artifacts<layer>_<region>_<level>and<layer>_<region>_<level>_json. The summary appears as a warning:Plan: X to add, Y to change, Z to destroy.2.<layer>_analyseand<layer>_approve: only in theApprove*modes. They pause for approval when the plan matches the mode. 3.<layer>_apply: applies the saved plan file, so what you approved is exactly what is applied. Skipped inPlanmode. 3. Clean: releases the runner reservation. - firewall JSON against
A plan that deletes resources marks the plan task as Succeeded with issues (orange). Treat that as a signal to read the plan.
What a spoke run does¶
To prepare the identity, service connections, code access and integrations for spoke deployments, see Prepare the spoke pipeline and Prepare spoke integrations.
- Prepare: runs the unit tests and the hub config tests (so a broken firewall JSON also blocks spoke runs), then the
spoke config tests. It finds
*.jsonunder<env-folder>/spokes/that matchspokeGloband groups them by spoke environment. The run fails if:- the glob matches no file;
- a matched file is not valid against
spokeschema.json; - a matched file's
environmentis not inenvironments; - a matched file's
managementGroupIdis not anidin<env-folder>/<targetRegions.primary>/foundation.auto.tfvars.
- Per spoke environment, in the order of
environments, and only after the previous one succeeded or was skipped:- Plan: plans and applies the subscription layer, then plans the spoke canvas.
- Analyse and Approve:
Approve*modes only. - Apply: applies the spoke canvas plan.
- Clean.
What the apply creates is described in Spoke canvas. To see afterwards whether a spoke still matches its file, see Configuration drift detection.
Approval gates¶
How the decision is made:
- Hub: per layer, the analyse step reads the plan summaries of the regions of that layer and asks for approval when a
plan matches the mode (
Invoke-XebiaHubAnalyseAction.ps1). Check the Known issues: in the version you pin, the analyse step may only read the last region's summary. - Spoke: one analyse step per spoke-environment stage. It reads every plan summary downloaded in that run, including subscription-layer plans and earlier environments. Approval is therefore per environment stage, not per spoke.
Where you approve:
- Azure DevOps: the
<layer>_approvejob is aManualValidation@1server job. The template sets noapproversand nonotifyUsers, so nobody is notified. Watch the run. - GitHub Actions: the approval job uses the GitHub environment
approval-required. Create this environment in the configuration repository and add required reviewers. Without reviewers the job continues without waiting.
Expose mode and whatIf at run time¶
Azure DevOps (pipelines/hub.yaml):
parameters:
- name: mode
type: string
default: Plan
values: [Plan, Apply, ApproveModifications, ApproveDestroys]
- name: whatIf
type: boolean
default: false
stages:
- template: .azure-devops/pipelines/templates/stage-mcp-hub.yaml@mcp-code
parameters:
# ...existing parameters...
mode: ${{ parameters.mode }}
whatIf: ${{ parameters.whatIf }}
GitHub Actions (.github/workflows/hub.yaml):
on:
workflow_dispatch:
inputs:
mode:
type: choice
options: [Plan, Apply, ApproveModifications, ApproveDestroys]
default: Plan
jobs:
canary:
uses: xmms/xms-cloud-platform-code/.github/workflows/stage-mcp-hub.yaml@release/v4.12
with:
# ...existing inputs...
mode: ${{ inputs.mode }}
Do the same on both platforms to keep parity.
Running a pipeline¶
Azure DevOps: Pipelines → the entry pipeline (for example hub) → Run pipeline → pick the branch → fill in the
parameters (for example spokeGlob) → Run. Open a plan job to read the Terraform plan, or download the plan artifact.
GitHub Actions: Actions → the workflow (for example spoke) → Run workflow → pick the branch → fill in the
inputs → Run workflow. Plan output is in the job logs and the uploaded artifacts.
Prerequisites for the pipelines¶
Azure DevOps
- Service connections with the names in
pipelines/variables.yaml(for example<env>.serviceConnectionNameHuband<env>.serviceConnectionNameSpoke). - A GitHub Enterprise service connection for the
mcp-coderepository resource (endpoint:in each entry file; in the reference repositoryxebia-managed-services-eu.ghe.com). - The agent pools
<AgentPool>(Managed DevOps Pool) and<AgentPool>-CTR(container jobs), created by theinfrastructurepipeline.
GitHub Actions
- A federated credential (OIDC) on the service principals in
mcp.json(Azure.TerraformState.Hub.ServicePrincipalIdand.Spoke.ServicePrincipalId). - Repository variable
cloud_platform_gh_app_idand secretcloud_platform_gh_app_private_key: the GitHub App that checks out the code repository (.github/actions/checkout-mcp-code). - The environment
approval-required.
Bootstrapping a new platform environment (order)¶
- Create the Terraform state storage account(s), the spoke state container and the deployment service principal(s), and grant them access. These are prerequisites, not created by the pipelines.
- Create
<env-folder>/mcp.json,global.auto.tfvarsand the region tfvars. - Run
infrastructure: job runners, plus the blob container in the hub state storage account. - Run
container: themcpimage. - Run
hub(first in Plan mode, then Apply). - Run
spokefor each spoke.