Config: What Configuration Do We Have¶
Each customer has one configuration repository. It holds only values; the Terraform lives in the code repository. There are two kinds of configuration:
| Hub | Spoke | |
|---|---|---|
| What it configures | The shared platform: management groups, network, firewall, policies, monitoring | One workload landing zone per file |
| Format | *.auto.tfvars + JSON (firewall, IP groups, App Gateway, WAF, policies) |
One JSON file per spoke |
| Pipeline | hub |
spoke |
| Read more | Hub config | Spoke config |
How to change either: Making a change safely (PR + CI + CD). The most frequent changes are listed under Common requests.
Reference pages for spokes: Spoke JSON reference, Spoke common settings, RBAC and PIM for spokes and Spoke integrations.
Conventions and review: Pull request review, Firewall rule and IP group guidelines and, if you run the optional module, Managed VM module.
Layout of a configuration repository¶
<config-repo>/
├── environments/
│ ├── canary/ ← <env-folder> for canary
│ │ ├── mcp.json Platform environment settings
│ │ ├── global.auto.tfvars Regions, environment names, platform subscription IDs
│ │ ├── <layer>.auto.tfvars Optional: values for one layer in every region
│ │ ├── <region>/ One folder per active region, e.g. gwc/
│ │ │ ├── global.auto.tfvars Region name and short name
│ │ │ ├── foundation.auto.tfvars
│ │ │ ├── connectivity.auto.tfvars
│ │ │ ├── firewall.auto.tfvars
│ │ │ ├── identity.auto.tfvars
│ │ │ ├── management.auto.tfvars
│ │ │ ├── postdeployment.auto.tfvars
│ │ │ ├── firewall_rule_configuration/*.json
│ │ │ ├── ip_groups/*.json
│ │ │ ├── application_gateways/*.json
│ │ │ └── web_application_firewall_policies/*.json
│ │ ├── firewall_rule_configuration/*.json Optional: global rule collection groups (all regions)
│ │ ├── ip_groups/*.json Optional: global IP groups (all regions)
│ │ ├── policies/ Definitions, set definitions, assignments, exemptions
│ │ └── spokes/<team>/*.json Spoke definitions
│ └── prod/ ← same structure
├── pipelines/ Azure DevOps entry pipelines + variables.yaml
└── .github/workflows/ GitHub Actions entry workflows
Two names are fixed by the code:
mcp.json. Its parent folder is the<env-folder>. The folder name becomes the environment name used in state keys (for examplecanary).spokes/directly under the<env-folder>.
All other folder names are set by *_relative_path / *_file_location variables in the tfvars.
mcp.json¶
Read by src/Xebia.Mcp/Functions/Public/Config/Resolve-XebiaMcpConfiguration.ps1. Keys marked required are
validated; GUID fields must be valid GUIDs.
| Key | Required | Purpose |
|---|---|---|
CustomerName |
yes | Display name of the tenant/customer. |
Azure.TenantId |
yes (GUID) | Entra ID tenant. |
Azure.TerraformState.Hub.{SubscriptionId, ResourceGroupName, StorageAccountName, ContainerName} |
yes | Backend for all hub layers. The storage account must already exist. |
Azure.TerraformState.Hub.ServicePrincipalId |
for GitHub, SPN login | Client ID used for Azure login (GitHub OIDC) and SPN login. |
Azure.TerraformState.Hub.KeyvaultName |
optional | Key Vault that stores SPN credentials (update-spn). |
Azure.TerraformState.Spoke.* |
yes (same fields) | Backend and identity for spoke deployments. |
Azure.JobRunner.{SubscriptionId, ResourceGroupName, Location} |
yes | Where the job runners are deployed. |
Azure.JobRunner.MaximumAgents, .Sku, .IpAddressPrefixes.* |
optional | Pool size, VM size and address ranges of the runner network. |
AzureDevops.OrganizationName, .AgentPool |
yes, unless a GitHub section is present |
Azure DevOps organization and Managed DevOps Pool. The container pool is <AgentPool>-CTR. |
AzureDevops.ContainerImageName |
optional | Images the container pipeline builds (normally ["mcp"]). |
GitHub.{AppId, AppInstallationId, AppPrivateKeySecretName} |
for GitHub runners | GitHub App used to register self-hosted runners. |
SpokeCommon.tenantinfo |
for spokes | AdministrativeUnitId, GroupnamePrefix (e.g. SG_SPOKE_), SPNNamePrefix (e.g. SPN-SPOKE-), SubsciptionNamingTemplate (the misspelling is the real key), SPNSecretrotationPeriod (days). |
SpokeCommon.shared_services |
for spoke extensions | Subscription and per-region resource IDs of shared APIM, ACR, Service Bus, Event Hub. |
The other spoke-wide settings (regions, IPAM pools, PIM profiles, GitHub instance) are described in Spoke common settings.
How relative paths in tfvars are resolved¶
Every layer receives -var config_path=<env-folder>, the folder that holds mcp.json. Terraform builds file paths as
"${var.config_path}/${var.<x>_relative_path}". Write relative paths from the <env-folder>:
# environments/prod/gwc/firewall.auto.tfvars
firewall_rule_collection_file_location = "gwc/firewall_rule_configuration" # correct
ip_group_json_file_relative_path = "gwc/ip_groups/" # correct
ip_group_json_file_relative_path = "environments/prod/gwc/ip_groups/" # wrong: resolves to environments/prod/environments/prod/…
A wrong path deletes resources
If a folder path is wrong, fileset() finds no files and does not fail. The plan then wants to delete every
resource that came from that folder (rule collection groups, IP groups, policy assignments). Always read the plan
summary before you apply. The reference configuration repository xms-cloud-platform-config currently uses
repository-root paths; see Known issues.
Which tfvars files a layer reads¶
For layer <layer> in region <region>, the plan passes these var files in this order and skips files that don't exist
(Invoke-XebiaHubPlanAction.ps1):
<env-folder>/<region>/<layer>.auto.tfvars<env-folder>/<region>/global.auto.tfvars<env-folder>/<layer>.auto.tfvars<env-folder>/global.auto.tfvars
When a variable is set in more than one file, Terraform uses the value from the file passed last. Put each variable in exactly one file.
An undeclared variable in a tfvars file is only a warning, so a typo is silently ignored. Check names against
infra/deploy/<layer>/variables*.tf.