Backwards Compatibility¶
Customers pin a version of the code repository and upgrade on their own schedule, sometimes several versions at once.
A release is backwards compatible when a customer can bump the version, run hub and spoke in Plan mode, and see
no changes they did not ask for.
The contract¶
| Version bump | Promise to the customer |
|---|---|
Patch (v4.12.1 → v4.12.2) |
Bug fixes only. No configuration changes needed. Plans show no changes, or only the fix. |
Minor (v4.11.x → v4.12.0) |
New features, all opt-in. Existing configuration keeps working. Any manual step is described in a version-specific upgrade guide. |
Major (v4.x → v5.0) |
Breaking changes are allowed, but each one is listed with its migration in the upgrade guide. |
Version bumps come from the merge message (+semver: major|minor|patch, see Releasing).
A breaking change merged with +semver: minor breaks the contract.
Rules for contributors¶
New features¶
- Put every new resource behind a feature flag (
use_<x>_module,create_<x>,enable_<x>), defaultfalse. See the feature flags reference for the existing pattern. - Give every new variable a default that keeps today's behavior. A new variable without a default forces every customer to change their tfvars.
- Make new JSON properties optional in the schema (
src/Xebia.Mcp/Files/json/*schema.json). A new required schema property fails the config test for every existing file.
Renames and refactors¶
- Never rename a Terraform resource or module address without a
movedblock. Examples in the code:module.virtual_wan[0]→module.virtual_wan(infra/deploy/connectivity/main.tf) andmodule.policy_assignements→module.policy_assignments(infra/deploy/postdeployment/main.tf). - When the key of a
for_eachchanges (for example from file path toname), amovedblock is not enough. Add a migration flag that keeps the old keys by default, such asuse_new_ip_group_naming,use_new_fwp_rulecollectiongroup_naminganduse_new_application_gateway_naming. Flipping such a flag is a migration, not a standard change. - Keep the old code path when you add a new one, and choose between them with a flag. The firewall layer still supports
the legacy single-folder layout next to the multi-region layout (
use_multiregion_fwp_rulecollectiongroup_naming). - Don't rename or remove a variable in a minor release. Deprecate it first: keep it, say so in its description, and
ignore it. For example,
create_virtual_wanis kept but documented as unused.
Provider and tool versions¶
- Root modules pin providers to an exact version.
scripts/Test-TerraformRootProviderPins.ps1checks this in the lefthook pre-commit hook and in the GitHubCIworkflow. - A provider major upgrade (for example azurerm 4 → 5) is a major platform version and needs an upgrade guide.
Pipelines and entry files¶
- Customers keep their own copy of the entry pipelines (
hub.yaml,spoke.yaml, …). A new required template parameter breaks their pipeline. Add parameters with a default. - Keep Azure DevOps and GitHub Actions in parity (
docs/PIPELINE_SYNCHRONIZATION.mdin the code repository).
How to check before you release¶
- Run
hubandspokein Plan mode against the Demo canary configuration without changing it. The plan must show no changes for existing resources. The code repository'spull-request.yamlpipeline does this for every pull request tomain(hub deploy and destroy, spoke deploy and destroy, all in Plan/WhatIf). - Plan against at least one real customer canary on the release branch.
- If any manual step is needed, write the upgrade guide before you tag the release.
When you cannot stay compatible¶
Sometimes a change cannot be made compatible, for example when Azure removes a policy or an API. Then:
- Bump the version as
+semver: major, or document the manual step in the minor release's upgrade guide if the impact is limited and unavoidable. - Describe each step in the upgrade guide: what to change in the configuration repository, what to remove in the Azure portal, and in which order.
- Mention it in the release notes under Breaking changes or Known issues.
Examples from earlier releases: v4.11.x removed the old DNS policy initiatives (files to delete in the configuration
repository and assignments to remove in the portal), and v4.12.x required a valid firewall_policy_id in every App
Gateway JSON. See What to do when a new version is released.
What the history shows¶
The contract above is the goal. The release history shows where earlier releases needed manual steps, and which kinds of change cause them:
| Kind of change | Example | Lesson |
|---|---|---|
| Policy or alert objects replaced by new ones | v4.4.0 (policy sets moved into the code repository), v4.5.1 (alert policies), v4.7.0 (Defender assignments), v4.11.x and v4.12.x (DNS policy) | The guides ask for the old objects to be removed by hand in the portal. List each one in the upgrade guide, with its scope, and say in which order to remove them |
| Files in the configuration repository that the code now owns | v4.4.0, v4.11.x | List the files to delete per environment folder |
| A new required setting with a default that does not fit every platform | v4.4.0 (state storage overrides), v4.9.0 (firewall rule folder variable) | Give it a default that keeps today's behavior. Where that is impossible, say in the guide how to check and what to set |
| A change in how paths or entry pipelines are written | v4.5.0, v4.9.0 | Customers own their entry files, so a change there is always a manual step. Keep entry file changes rare |
| A property must now hold a valid value | v4.12.x (firewall_policy_id) |
Check existing files in a validation step, and say how to fix them |
Use the table as a review checklist when you decide whether a change can be a minor version. The upgrade guides are in Platform upgrade guides.