Making a Change Safely (PR + CI + CD)¶
Every configuration change, hub or spoke, follows the same procedure. Every runbook under Common requests builds on it.
The procedure¶
1. Capture the request¶
Collect the request fields listed in the runbook. Keep the request (ticket, work item or issue) and link it from your pull request. This is your change record.
2. Create a branch in the configuration repository¶
git checkout main
git pull
git checkout -b change/<ticket-id>-<short-description>
3. Edit the configuration¶
Each platform environment has its own folder under environments/. Make the same change in every environment folder
that should get it, and apply it to canary first.
Change only the files the runbook names. Keep JSON valid: two-space indentation, no trailing commas, extension .json.
Note
Files that don't end in .json are ignored by every loader (/**/*.json, Get-ChildItem -Filter '*.json'). To
disable a file without deleting it, rename it to <name>.json.ignore.
4. Validate before you push (optional, recommended)¶
Run the same config tests the pipeline runs (see Run the tests locally):
$env:MCP_CONFIG = '/workspaces/config/environments/canary/mcp.json'
pwsh ./dev/dev.ps1 -Action Test
5. Open a pull request (PR)¶
Describe the change, link the request, and ask a second engineer to review. Reviewers check:
- that the change matches the request;
- that names and priorities are unique;
- that nothing outside the request changed.
6. Plan against canary (CI)¶
Run the entry pipeline from your branch in a mode that does not apply without you: Plan or ApproveModifications
(see Pipelines and workflows). Read each layer's plan:
- Expected: only the resources named in the runbook appear as add or change.
- Stop if you see unexpected
destroyactions, changes in layers the runbook doesn't mention, or a whole folder of resources being removed. A removed folder usually means a wrong path in tfvars.
7. Apply to canary, verify, then prod (CD)¶
- Approve or apply canary.
- Do the runbook's Verify steps.
- Let the run continue to prod, or start it for prod, and review the prod plan the same way.
8. Merge¶
Merge the pull request so main always describes what is deployed. If you deployed from the branch, merge right after a
successful apply. Otherwise the next run from main reverts your change.
Rollback¶
The configuration is declarative, so rolling back means making Git describe the old state and applying it again:
git revert <merge-commit>
Then run the same pipeline again. Some resources cannot simply come back:
- a deleted Key Vault with purge protection (it is recovered on re-create, with its old secrets)
- a cancelled subscription
- deleted Entra ID groups and service principals
Run the tests locally¶
The dev container of the code repository (.devcontainer/devcontainer.json) has the toolchain the pipelines use
(PowerShell 7, Terraform 1.14.4, Azure CLI, Pester). It mounts ../config, relative to the code repository, at
/workspaces/config. Clone your configuration repository next to xms-cloud-platform-code as config.
- Open the code repository in the dev container (VS Code: Reopen in Container).
-
Create
.envfrom.env.exampleand setMCP_CONFIG:MCP_CONFIG=/workspaces/config/environments/canary/mcp.json -
Run the tests:
pwsh ./dev/dev.ps1 -Action Test
Test runs the module parity checks and unit tests, then the config tests for your mcp.json: firewall and IP groups,
App Gateway, WAF and the DNS baseline.
Run a single layer locally¶
The local functions run the same plan and apply code as the pipelines, against the environment in MCP_CONFIG, with
your own Azure login.
dev.ps1 hub and spoke actions
On main and release/v4.12, dev/dev.ps1 -Action HubDeploy (and HubUnlock, SpokeDeploy, …) is rejected by its
ValidateSet, and the spoke actions are hard-coded to one Demo file. Until the fix is merged, call the functions
directly as shown below. See Known issues.
# In the dev container, from the root of xms-cloud-platform-code
Import-Module ./src/Xebia.Mcp/Xebia.Mcp.psd1 -Force
Initialize-XebiaDotEnv
Sync-XebiaInfraToMcpModule -Mappings @(@{ Description = 'cloud-platform'; Source = './infra'; Target = 'terraform/cloud-platform/' }) -WhatIf:$false
$config = Resolve-XebiaMcpConfiguration -Path $env:MCP_CONFIG
# Plan only the firewall layer in gwc (WhatIf on = no apply)
$global:WhatIfPreference = $true
Deploy-XebiaLocal -Config $config -DeployName firewall -Region gwc
# Plan and apply it
$global:WhatIfPreference = $false
Deploy-XebiaLocal -Config $config -DeployName firewall -Region gwc
# Plan one spoke (path relative to <env-folder>/spokes/)
$global:WhatIfPreference = $true
Deploy-XebiaLocal -Config $config -SpokeFilePaths @('team-a/canvas_app1_dev.json')
# Release a stuck state lease
Unlock-XebiaLocal -Config $config -DeployName firewall -Region gwc
| Function | What it does |
|---|---|
Deploy-XebiaLocal |
Plan (and apply when WhatIf is off) hub layers (-DeployName, -Region) or spokes (-SpokeFilePaths) |
Remove-XebiaLocal |
Destroy, in reverse layer order |
Unlock-XebiaLocal |
Break a stuck Terraform state lease |
Tip
Use local runs for diagnosis and for canary. For prod, use the pipelines, so every change has a run history and an approval.