Decision Summaries of the Template Library¶
The library records its design decisions as decision records, in the Markdown format MADR. This page summarises them: the problem, the choice, and the consequences. The full records, with all options and arguments, are kept in the repository of the library. The decisions are numbered; the status is accepted unless stated. Where the current repository shows how a decision works out, a line In the repository says so.
| # | Decision | Status |
|---|---|---|
| 0000 | Record decisions in MADR | Accepted |
| 0001 | Repository structure | Accepted |
| 0002 | Shared resources during tests | Accepted |
| 0003 | Compliance testing with Azure Policy | Accepted |
| 0004 | Bicep testing tool: Pester | Accepted |
| 0005 | Terraform testing tool: Terratest | Accepted |
| 0006 | Versioning: per module | Accepted |
| 0007 | Forks of upstream modules for urgent fixes | Proposed (tested in practice) |
| 0008 | Parameterised policies | Accepted |
0000: Record decisions in MADR¶
Problem. Decisions about architecture, code and other topics need a format and a structure. Options. MADR 3.0.0, the template of Michael Nygard, the Y-statements of sustainable architectural decisions, other templates, no convention. Choice. MADR 3.0.0. It makes implicit assumptions explicit, captures any decision in a structured way, is lean, easy to read and to maintain, and the project is alive. Consequence. Every decision has the same shape (context, drivers, options, outcome, consequences), so a later reader can follow the reasoning.
0001: Repository structure¶
Problem. The library needs a predictable structure that automation can rely on.
Drivers. Simple structure that supports automation. Grouping by technology (Bicep and Terraform).
Choice. The Terraform module structure, adapted: folders for examples, modules, pipelines and tests, each split by
technology and by module.
Consequence. Clear division by functionality, technology and module. The structure is easy to change early on. A similar
convention is used by the Azure Verified Modules project. See Repository structure.
In the repository: the structure has grown with scripts/, iac_accelerator/ and utils/, next to the four folders of the decision.
0002: Shared resources during tests¶
Problem. Most modules need the same dependencies (network, workspace, Key Vault, storage). Deploying them in every test
makes the tests slow.
Options. Use shared resources, or deploy all dependencies per test.
Choice. Shared resources, created once from a dependencies folder, with their data passed to the tests as JSON.
Consequences. Short tests, one place to change the dependencies, reuse by Terraform and Bicep tests, and the dependencies
are not part of what the tests check. The price is more complexity and no editor autocomplete on the shared values.
See Testing.
0003: Compliance testing with Azure Policy¶
Problem. The templates must meet the baselines of the platform (CIS, the Microsoft cloud security benchmark, ISO 27001), and the library needs a way to test that. Options. Static analysis of the code (Checkov, Tfsec, Trivy, Open Policy Agent, Terrascan), or testing the compliance of the deployed resources with Azure Policy. Choice. Azure Policy. It does not depend on the language, it is simple, and the policies are the single source of truth. Consequences. The library does not have to maintain its own rules, and the test could be built quickly. Testing takes longer, because resources are deployed and evaluated. Static analysis had false positives (for example on nested modules and loops), missing checks and weak Bicep support, and can be added later if the test time becomes a problem. See Testing. In the repository: every test calls the same policy check script. A short list of policies is ignored per module, each with a reason (Ignored policies, Known findings).
0004: Bicep testing tool¶
Problem. Every template must be deployed and the resources removed afterwards, with the lowest possible test time and little implementation effort. Options. Pester, or a custom PowerShell loop with parallel deployments. Choice. Pester, with a script for parallel runs. It needs little work to capture the output and to produce a report for the pipeline, and it gives a standard for the tests of new modules. Consequences. The total test time in the report is not correct, and the log shows the output only when all tests are done. In the repository: a runner script starts one thread job per test file and merges the results into one JUnit report.
0005: Terraform testing tool¶
Problem. The same as for Bicep, for Terraform.
Options. Terratest, or the native terraform test.
Choice. Terratest. Proven and mature, flexible, and it runs tests in parallel.
Consequences. Parallel tests (the CI pipeline runs up to ten at the same time), and the tool can be used for other types of tests later. It needs some knowledge of Go to
set up the first structure. terraform test was simple and native, but has no parallelism, less flexibility and less
maturity.
0006: Versioning strategy¶
Problem. Consumers must be able to decide consciously when they upgrade, instead of always using the latest code.
Options. One version for the whole solution, one per technology, or one per module.
Choice. Per module, with semantic versioning and git tags such as bicep/storage_account/1.0.1, because the other
two give misleading numbers.
Consequences. Version numbers that tell the truth, at the price of a more complex release process. A survey of other
shared module repositories found that monorepos mostly use one version per repository, and that nobody combined Terraform and
Bicep modules in one product. The team treats the choice as an experiment and expects to learn from it.
See Versioning.
In the repository: tags have the form terraform/<module>/<version> and bicep/<module>/<version>, and the patch number is set by the release script (Tags).
0007: Forks of upstream modules for urgent fixes¶
Status. Proposed. Problem. The modules call Azure Verified Modules, which have minor bugs. Fixes often wait months in review upstream, and a workaround is not always possible. The library also wants similar Terraform and provider versions across modules, which would require contributions to many modules with different owners. Options. Use AVM only as a reference and copy the code. Contribute to AVM and wait. Fork and maintain AVM repositories. Contribute to AVM, and use forks in the library when the review takes too long (a mix). Choice. The mix: contribute upstream, and call a fork of the module from the library when the review takes too long or an urgent incident needs it. The team wants to stay close to AVM and give value back, instead of maintaining the modules in full. Consequences. The library keeps using AVM, gives value back and can ship a quick fix. A complete change takes more time and effort, and updating the called AVM version still takes work. The team tests whether this works in practice. See Bugs in upstream modules. In the repository: the Terraform container instance module calls a fork of the AVM module, and the Terraform container app module has moved from wrapping another module to owning its resource logic (Module catalogue).
0008: Parameterised policies¶
Problem. The modules fix the values that a policy checks, so that users cannot deploy non-compliant resources. Some
policies take parameters that each platform sets differently, and some have no default value.
Options. Use the default as a constant, and use an input variable when there is no default. Always use an input variable.
Only document the cases that cannot be fixed. Or the first option plus documentation of the cases where a constant is needed.
Choice. The default parameter value as a constant in the module. When the policy has no default, an input variable.
All cases in which the value cannot be enforced are documented.
Consequences. The modules are as secure as the standards require. They fit fewer situations, so a team may have to make a
copy of a module for a special case, with extra work. See Parameterised policies.
In the repository: the fixed values are visible in the locals.tf of a module (Policy compliance).