Change a Spoke¶
Standard change: Changes to spoke canvas. This covers the RBAC owner, service principal information, networking, and so on.
Effect: Terraform reconciles the existing spoke with its edited JSON file. Only the differences are applied.
Request fields¶
Same as New spoke: Name, Rationale, Environment, Usage type, Exposure to public internet, IP range/size, subnet name/size/delegations/service endpoints, which team needs access, and Comments. Fill in only the fields that change.
How it works¶
There is no separate "update" pipeline. You edit the spoke's JSON file and run spoke for that file. The spoke keeps its
identity through its Terraform state key, spoke_<workloadShortName>_<environment>.tfstate. So:
Never change
workloadShortNameorenvironmentof an existing spoke. Either change creates a new state key and a full second spoke, while the old one stays as it is. Renaming the file or moving it to another folder is safe: the file name is only used for job and artifact names.
What you can change, and what happens¶
| Change | JSON field | What the plan should show |
|---|---|---|
| Add a subnet | append to network[].subnets[] |
subnet created + NSG association (+ route table association) |
| Change subnet delegations or service endpoints | subnets[].delegations / .serviceEndpoints |
subnet updated |
| Change a subnet or VNet address range | subnets[].addressPrefix, network[].addressPrefix |
Update or replace. Azure refuses to resize a subnet that has resources in it. If the plan says must be replaced, stop and plan a migration. |
| Add or change NSG rules | networksecuritygroups[].rules[] |
rules created or updated. Don't change the priority of an existing rule: the rule is recreated, and that can fail and leave the NSG inconsistent. Add a new rule with a new name and a new priority, then remove the old rule in a later change. |
| Add a route table / routes | routetables[], and subnets[].linkedroutetablename |
route table + association |
| Grant another team access | append to adInfo.ProductTeamGroupNames |
PIM eligibility or active membership for the new group |
| Change PIM behavior | adInfo.PIMGroupConfig.{Readers,Contributors,Owners} |
PIM settings updated |
| Change who can use a data-plane role (Key Vault, Storage, others) | adInfo.PIMAssignments |
Role assignments and PIM eligibility at subscription scope. See RBAC and PIM. |
| Add an Azure DevOps repository and service connection | azdo |
New repo and service connection. Prepare first: Spoke integrations. |
| Add a GitHub repository with federated credentials | extensions.github |
New repo, federated credentials, Actions secrets. See Spoke integrations. |
| Add an environment to the GitHub federated credentials | extensions.github.environments |
One more federated credential |
| Switch a service connection to federated credentials | azdo.useFederatedCredentials |
The service connection uses workload identity federation. No secret is generated. Read the plan for what changes. |
| Resize an IPAM-allocated network | numberOfIpAddresses |
Treat it like a change of address range: read the plan and stop on any replace. |
| Change tags (team-owned) | tags |
subscription and resource tags updated |
| Move the subscription to another management group | managementGroupId |
management group association |
| Rename the subscription | azureSubscriptionName |
subscription rename |
| Enable or disable hub internet routing | network[].InternetSecurityEnabled |
hub connection updated |
| Add a region | targetRegions.additional + a network[] entry with that region |
per-region resource group, VNet, LAW, KV, storage |
Changing the owner (RBAC owner)¶
The spoke's Entra ID groups are created with owners taken from adInfo.ad_OwnersMembers (e-mail addresses) and
adInfo.ad_OwnersMembersUPN. In modules/ad_groups/main.tf, owners and members are under
lifecycle { ignore_changes = [owners, members] }. Editing these lists after the first deployment has no effect.
Change ownership instead by:
- changing who is in the team groups listed in
ProductTeamGroupNames(Entra ID), or - adding a different team group to
ProductTeamGroupNamesand removing the old one, then running the pipeline, or - changing the
SG_SPOKE_<wl>_<env>_*group owners directly in Entra ID. Terraform ignores these, so the change is not reverted, but it is not recorded in Git either. Record it in the change ticket.
Changing service principal information¶
The service principal <SPNNamePrefix><wl>-<env> is managed by the spoke canvas:
- Rotate the secret: the secret is stored in the spoke Key Vault unless federated credentials are configured. It is
regenerated by a
time_rotatingresource (modules/ad_service_principal/main.tf) withrotation_days = SpokeCommon.tenantinfo.SPNSecretrotationPeriod. The new secret is created on the first spoke run after the period has passed, so run the spoke pipeline regularly, or the secret expires. - Switch the Azure DevOps service connection to workload identity federation: set
azdo.useFederatedCredentialstotrue. - Graph permissions:
adInfo.SPNGraphPermissionsis read by the Terraform. The schema does not define it, but allows it becauseadInfoaccepts extra keys. Review the plan carefully.
Steps¶
- Edit the spoke JSON in a branch (see Making a change safely). A standard branch name helps,
for example
feature/SpokeUpdate-<request id>-<workload short name>-<environment>. Some changes are not possible because of limits in Terraform or Azure. For example, you cannot change a subnet that virtual machines use. - Run
spokewithspokeGlobset to that one file. Preferably use modeApproveModifications, so you can read the plan before it is applied. The reference entry files hard-codemode: Apply; see Expose mode and whatIf at run time. - Read the plan. Expect only the changes from the table above. Stop on any
destroy/must be replacedthat you did not intend. That includes a destroy ofazurerm_management_group_subscription_association(a known issue, see Known issues). - Approve or apply.
Verify¶
Check the changed item in the Azure portal or Entra ID, and ask the requesting team to confirm.
Roll back¶
Revert the JSON change and run spoke for the file again.