Skip to content

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>), default false. 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 moved block. Examples in the code: module.virtual_wan[0] → module.virtual_wan (infra/deploy/connectivity/main.tf) and module.policy_assignements → module.policy_assignments (infra/deploy/postdeployment/main.tf).
  • When the key of a for_each changes (for example from file path to name), a moved block is not enough. Add a migration flag that keeps the old keys by default, such as use_new_ip_group_naming, use_new_fwp_rulecollectiongroup_naming and use_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_wan is kept but documented as unused.

Provider and tool versions

  • Root modules pin providers to an exact version. scripts/Test-TerraformRootProviderPins.ps1 checks this in the lefthook pre-commit hook and in the GitHub CI workflow.
  • 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.md in the code repository).

How to check before you release

  1. Run hub and spoke in Plan mode against the Demo canary configuration without changing it. The plan must show no changes for existing resources. The code repository's pull-request.yaml pipeline does this for every pull request to main (hub deploy and destroy, spoke deploy and destroy, all in Plan/WhatIf).
  2. Plan against at least one real customer canary on the release branch.
  3. 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:

  1. 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.
  2. 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.
  3. 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.