Use Managed Templates (Shared Modules)¶
Some platforms offer a library of managed templates: Terraform modules and Bicep modules, delivered from a shared location. They give workload teams a starting point that follows the platform's standards and works with the spoke baseline. The library is not part of the MCP product itself. Whether your platform has one, where it is and how it is versioned is set by your platform team.
What the library offers¶
| What | Meaning for you |
|---|---|
| A module per resource type, in Terraform and in Bicep | You call a module instead of writing the resource yourself. Each module has an example. Not every module exists in both technologies: see the module catalogue. |
| Compliant by design | Values that an Azure Policy checks are fixed in the module, so you cannot deploy a resource that the baseline denies. See Policy compliance. |
| Tested | The platform team deploys every module in a test subscription and checks the result against the policies of the platform. |
| Versioned per module | Every module has its own version (a git tag such as bicep/storage_account/1.0.1) and its own changelog. You choose when to upgrade. |
| Documented | Each module has generated documentation with all parameters and outputs, in the folder of the module in the repository. |
| An example project | The IaC Accelerator shows how to structure a project, call the modules and deploy with a pipeline. |
The templates are not a replacement for design. They help you comply: the policies of the platform still apply to everything you build (Policy and exemptions). How the library is built and maintained is described in Managed Templates (platform side).
A module can be stricter than you expect
A module fixes the settings that a policy checks. If a value does not suit your case (for example a setting that the module always switches on), you cannot change it through a parameter. Some values are a parameter because the policy has no default. Check the documentation of the module first, then ask your platform team. They can extend the module, or you can make your own copy of it and take the extra work on yourself. A policy exemption is a last resort (Request a policy exemption).
How the modules reach you¶
Your platform team delivers the library in two ways. Ask them which one applies to you. The person who arranges this on your side is the XMT Ambassador (Set up access). New to the library or to infrastructure as code? Start with the IaC Accelerator.
| Technology | Delivery |
|---|---|
| Terraform (and the examples and documentation) | A read-only copy of the repository in your environment, with the main branch and all tags. You reference a module by tag, as shown below. |
| Bicep | The modules are published to a container registry in your tenant at each release. You reference a module by its version in the registry. |
Membership of a reader group in Entra ID gives people access to the copy and to the registry. How to get added is described in Set up access.
Deploy through code
The templates are meant for infrastructure as code. Manual deployments of resources in acc and prd are usually not
allowed. See How to use.
Reference a module¶
Every module has a README in its folder with a short example, the parameters and the outputs. Start with the minimum set of required parameters and add options when you need them. Switch to the tag of the version you use before you read the documentation, because the documentation changes with the module. Report documentation gaps to your platform team.
The repository, project, registry and module names below are placeholders.
Terraform¶
Point the source of the module at the repository and pin a version tag with ref, so that a new module release
doesn't change your infrastructure unexpectedly. The tag is the full tag name: terraform/<module>/<version>.
module "storage_account" {
source = "git::https://dev.azure.com/<organisation>/<project>/_git/<repository>//modules/terraform/storage_account?ref=terraform/storage_account/<version>"
# other parameters
}
Bicep¶
Reference the module in your container registry. Use only the version number (the last part of the tag name):
module storageAccount 'br:<registry>.azurecr.io/modules/bicep/storage_account:<version>' = {
name: 'storageAccountDeployment'
params: {
// required parameters first, then optional parameters
}
}
Both calls accept the same kind of settings: names, the subnet for the private endpoint, the Log Analytics workspace for diagnostics, identities and a customer-managed key where the module supports it. The module README of each technology has a full example.
Deploy¶
| Where | Terraform | Bicep |
|---|---|---|
| Locally | terraform init, plan, apply. When resources have public access disabled, connect through a VPN to Azure first. |
az deployment at the scope of the template |
| In a pipeline | A service connection with the rights for your deployment, and the access to the repository below | A service connection with the rights for your deployment, and access to the registry below |
The IaC Accelerator has complete pipelines, with a plan stage and an approval for Terraform, and an optional what-if for Bicep. Some features of a deployed resource need firewall rules (for example VM updates for the Windows VM module). The known requirements are in the README of the module (Firewall rules).
Give Terraform access to the repository (your laptop)¶
If your organisation only allows HTTPS with OAuth to Azure Repos, Terraform needs a Git credential helper to download the
modules. Set it globally, because Terraform runs git itself:
git config --global credential.helper ''
git config --global --add credential.helper <path to git-credential-manager>
git config --global credential.azreposCredentialType oauth
The path of the credential manager depends on how you installed it.
Give the pipeline access to the repository¶
This section is about Terraform pipelines. For Bicep, see Give a Bicep pipeline access to the registry. Choose one of two options. The IaC Accelerator uses option 2.
Option 1: check out the repository and rewrite the URL¶
Declare the template repository as a resource and check it out next to your own repository. When you list a checkout
step yourself, the default checkout stops, so also check out self:
resources:
repositories:
- repository: shared-tf-modules
type: git
name: "<project>/<repository>"
steps:
- checkout: self
- checkout: shared-tf-modules
path: "s/shared-tf-modules"
fetchDepth: '0' # needed to switch to the tag in `ref`
fetchTags: 'true'
Rewrite the module URL to the local checkout, so terraform init uses the local copy:
- bash: |
git config --global url."file://$(Pipeline.Workspace)/s/shared-tf-modules".insteadOf "https://dev.azure.com/<organisation>/<project>/_git/<repository>"
displayName: 'Configure path to shared Terraform modules'
Clean up afterwards, so the rewrite doesn't stay on a shared agent:
- bash: |
for url in $(git config --global --get-regexp "^url" | sed 's/.instead.*//')
do
git config --global --remove-section $url
done
displayName: 'Clean up git config'
condition: always()
Option 2: give git the access token of the pipeline¶
Declare the repository as a resource in the pipeline, then let git send the access token of the pipeline as a bearer token to your Azure DevOps organisation, for the time that Terraform runs. Remove the setting afterwards:
resources:
repositories:
- repository: <repository>
type: git
name: <project>/<repository>
steps:
- script: |
git config --global --add http.${{variables['System.CollectionUri']}}.extraheader "AUTHORIZATION: bearer $(System.AccessToken)"
displayName: 'Enable git authentication for XMT'
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
# terraform init and the other Terraform steps
- script: |
git config --global --unset-all http.${{variables['System.CollectionUri']}}.extraheader
displayName: 'Disable git authentication'
condition: always()
This option has requirements in Azure DevOps:
- The build identity of the pipeline needs read access to the template repository. Which identity that is depends on the organisation setting Limit job authorization scope to current project for non-release pipelines: the project collection build service when it is off, the build service of the project when it is on. The XMT Ambassador adds this identity to the access group (Grant pipeline access).
- The project setting Protect access to repositories in YAML pipelines must be switched off.
If Terraform cannot download the modules, check these two points first. Option 2 puts the token in the git configuration of the agent, so use the cleanup step and prefer a build agent that is not shared with other jobs.
Give a Bicep pipeline access to the registry¶
The service connection that the Bicep deployment job uses must be a member of the reader group of the library in Entra ID. The agent needs network access to the container registry on port 443, over ExpressRoute or a VPN, or through an address that the registry allows (Set up access).
Choose and upgrade versions¶
- Pin the version of each module you use. Don't point at a branch.
- Read the changelog of the module before you change the version. A change of the
MAJORnumber can break your call, a change of theMINORnumber adds options, and aPATCHnumber fixes errors (Versioning). - Some modules have upgrade notes for a major version, with the steps for your code and your state (Upgrade notes).
- Test the new version in
devfirst, as with any other change (How to change). - You can let a bot propose the new versions. The accelerator contains a Renovate configuration for that (Keep modules up to date).
Provider versions¶
A module declares which provider versions it supports. If a module still limits the azurerm provider to an older major
version, you can't use a newer provider in the same configuration. Check the required_providers block of the module
before you start, and ask your platform team for the planned update if it doesn't match your version. Some modules also use
the azapi provider, which your root module must then declare and configure.
Frequently asked¶
How do I assign a role to the identity that deploys a Bicep template? Use the roleAssignments parameter of the module,
with deployer().objectId as the principal. deployer() needs Bicep 0.32.4 or newer.
roleAssignments: [
{
principalId: deployer().objectId
roleDefinitionIdOrName: 'Key Vault Administrator'
principalType: 'ServicePrincipal'
}
]
Why does the policy overview still show findings for a deployed module? Some policies report non-compliance that a module cannot prevent, or that needs time. See Known findings.
Ask for a change¶
Missing a module, a parameter or documentation? Ask your platform team (Support). A new module follows the creation process of the library, with an example, tests and documentation (Create and contribute modules).