Create and Contribute Modules¶
This page is the contribution guide of the template library: how to create a new module, how to treat bugs in the upstream modules (Azure Verified Modules), how to handle policy-checked values, and what to check before you open a pull request.
Who can contribute¶
Engineers of the maintaining team can contribute. In the reference setup access is given through two groups in Entra ID:
| Group | Gives |
|---|---|
| Contributors | Write access to the library repository, and the contributor role on the test subscription |
| Readers | Read access to the repository |
Every contribution is a pull request on the repository, reviewed by a second person, with passing tests. The repository has code owners and a pull request template with a checklist (Before you open a pull request). The development container in the repository gives you the tools: Terraform, terraform-docs, Azure CLI with Bicep, PowerShell, Go and Git (Pipelines and scripts).
Create a new module¶
A module wraps one module of the Azure Verified Modules (AVM) and fixes the values that the policies check. Some modules own their resource logic instead (see the module catalogue), and pattern modules combine other modules of the library. This guide is for the first kind.
- Find the module in AVM (index of Azure Verified Modules).
-
Create the folder structure. Use the scaffolding script, so that all files are in the right place (Pipelines and scripts):
- Terraform:
New-TerraformModulecreates the module folder withmain.tf, emptyvariables.tfandoutputs.tf,_header.md,CHANGELOG.md(starting at 0.1.0),version.jsonand the docs example, plus the Terratest test and the example, with the module name filled in. - Bicep:
Set-Modulecreates the missing files and folders for the module when you run it on the module path:main.bicep,version.json, the docs example, the changelog, the example and the Pester test.
Place the module in the right folder in
modules/<technology>/(Repository structure). 3. Copy all parameters (inputs) of the source module into the new module. 4. Generate the list of inputs of the source module, so that you can pass all of them on to the call. In PowerShell:Terraform:
$var_names = ((Get-Content variable* | Select-String -Pattern 'variable "(?<name>.*)" {').Matches.Groups | Where-Object Name -eq name).value $var_names | ForEach-Object -Process {"$_ = var.$_"} | Sort-ObjectBicep:
$var_names = ((Get-Content main* | Select-String -Pattern 'param (?<name>\w+) .*').Matches.Groups | Where-Object Name -eq name).value $var_names | ForEach-Object -Process {"$_`: $_"} | Sort-Object - Terraform:
-
Call the AVM module in your module, and pass all arguments with the list from step 4.
-
Copy all outputs and give them the values of the outputs of the AVM module. For example:
Source module:
output "vnet_id" { description = "The id of the newly created vNet" value = azurerm_virtual_network.vnet.id }New module:
output "vnet_id" { description = "The id of the newly created vNet" value = module.vnet.vnet_id } -
Review the Azure policies that apply to the resources of the module, and see which properties they check.
- Fix those properties. Set constant values for them with a local or a variable with a fixed value, and pass these to the call instead of a variable. The user can then not pick a value that breaks the policy (Parameterised policies).
- Remove the inputs that you no longer use.
- Write the example in
examples/<technology>/<module>that calls the new module. Read shared resources (subnet, workspace, private DNS zones) from the shared resources file (Testing). - Write the tests in
tests/<technology>, based on the existing tests (Testing). If a policy cannot be met and you ignore it in the test, write down why, in the test and in the notes of the module (Ignored policies). - Run the tests with the CI pipeline, or try the example with the manual deploy pipeline first (Pipelines and scripts).
- Document the module. Write the features, limitations and policy notes in
_header.md(Terraform), and let the pipeline generate the rest (Release and documentation). For Terraform you can runterraform-docswith the configuration in the repository, and for Bicep theSet-Modulescript. - Write the changelog and the version. A new module starts at
0.1. ChangeMAJORorMINORinversion.jsonfor later changes as needed (Versioning). A breaking change gets upgrade notes in theupgrade/folder of the module.
Parameterised policies¶
Some policies have parameters that are set when the policy is assigned (for example a list of allowed values), so each platform can use a different value. A module has to know which value to fix. The rule:
| Situation | What the module does |
|---|---|
| The policy has a default value for the parameter | Use the default as a constant in the module. |
| The policy has no default | Use an input variable in the module. The user chooses the value. |
| A value cannot be fixed because it depends on the policy parameter | Document it, so that the user knows. |
This gives the most secure modules for the standards that the library is built for. The cost: the modules fit fewer situations. When the fixed value does not suit a team, the team can make its own copy of the module and take the extra work on itself, or ask for a change. The library does not claim to fit every case.
Bugs in upstream modules¶
The modules call AVM modules, and AVM modules have bugs. Fixes often wait a long time for a review in the upstream repository. The rule of the library is a mixed approach:
- Contribute the fix to AVM: that is good for the project and for every user. Contributing needs knowledge of the AVM process, a test subscription for the end-to-end tests, and time of the module owner.
- If the review takes too long, or an urgent incident needs the fix, call a fork of the AVM module from the library, with the fix applied.
- When you find a bug, first try a workaround in the module of the library. Use a fork only when that is not possible.
An example of the fork route in the reference repository is the Terraform container instance module, which calls a fork of the AVM module that the maintainers keep. Consequences to keep in mind: you have to follow the AVM process anyway, updating the AVM version that the library calls still takes effort, and forks have to be synchronised with upstream. The decision is proposed, which means that the team tests it in practice (Decision summaries).
Also keep the versions of Terraform and of the providers similar across modules, so that teams do not get version conflicts when they combine modules (Provider versions).
Rules of engagement¶
These are the contribution rules of the platform code. They are a good default for the library as well, where they fit:
- Only items from the backlog are picked up. A new idea goes to the backlog first.
- Code is written in Terraform (or Bicep, in the Bicep part of the library).
- All settings are passed as variables, and each variable has a default value. Fixed values for policy-checked properties are the exception (Parameterised policies).
You can contribute in three ways: give feedback on what is built and how, propose new ideas (features or improvements), or pick up and deliver backlog items.
Before you open a pull request¶
- [ ] The module follows the structure: module, example and tests in their folders.
- [ ] All parameters and outputs of the source module are passed on, and unused inputs are removed.
- [ ] Policy-checked values are fixed, or documented as a variable.
- [ ] The example deploys, and the tests pass for this module and for the others (CI pipeline).
- [ ] The compliance test shows no non-compliant policy.
- [ ] The module documentation is regenerated (the CI pipeline commits it to your branch).
- [ ] The changelog has a line for the change, for each modified module.
- [ ]
MAJORorMINORare changed inversion.jsonwhen the change needs it (Versioning), and a breaking change has upgrade notes. - [ ] Tests are added when the module is new.
- [ ] The pull request is squashed on merge, so that the release pipeline can read the change.