Skip to content

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.

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 destroy actions, 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)

  1. Approve or apply canary.
  2. Do the runbook's Verify steps.
  3. 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.

  1. Open the code repository in the dev container (VS Code: Reopen in Container).
  2. Create .env from .env.example and set MCP_CONFIG:

    MCP_CONFIG=/workspaces/config/environments/canary/mcp.json
    
  3. 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.