Terraform Composition
Important
While this page describes and summarizes important aspects of the composition of AVM modules, it may not reference All of the shared and language specific requirements.
Therefore, this guide MUST be used in conjunction with the Terraform specifications. ALL AVM modules (Resource and Pattern modules) MUST meet the respective requirements described in these specifications!
Important
Before jumping on implementing your contribution, please review the AVM Module specifications, in particular the Terraform specification pages, to make sure your contribution complies with the AVM module’s design and principles.
Repositories
Each Terraform AVM module will have its own GitHub Repository in the Azure
GitHub Organization as per SNFR19.
This repo will be created by the Module Owners and the AVM Core team collaboratively, including the configuration of permissions as per SNFR9
Directory and File Structure
Below is the directory and file structure expected for each AVM Terraform repository/module.
See template repo here.
tests/
- (for unit tests and additional tests if required - e.g. tflint etc.)unit/
- (optional, may use further sub-directories if required)
modules/
- (for sub-modules only if used)examples/
- (all examples must deploy without successfully without requiring input - these are customer facing)<at least one folder>
- (at least one example that uses the variable defaults minimum/required parameters/variables only)<other folders for examples as required>
/...
- (Module files that live in the root of module directory)_header.md
- (required for documentation generation)_footer.md
- (required for documentation generation)main.tf
locals.tf
variables.tf
outputs.tf
terraform.tf
README.md
(autogenerated)main.resource1.tf
(If a larger module you may chose to use dot notation for each resource)locals.resource1.tf
Directory and File Structure
/ root
│
├───.github/
│ ├───actions/
│ │ ├───avmfix/
│ │ │ └───action.yml
│ │ ├───docs-check/
│ │ │ └───action.yml
│ │ ├───e2e-getexamples/
│ │ │ └───action.yml
│ │ ├───e2e-testexamples/
│ │ │ └───action.yml
│ │ ├───linting/
│ │ │ └───action.yml
│ │ └───version-check/
│ │ └───action.yml
│ ├───policies/
│ │ ├───avmrequiredfiles.yml
│ │ └───branchprotection.yml
│ ├───workflows/
│ │ ├───e2e.yml
│ │ ├───linting.yml
│ │ └───version-check.yml
│ ├───CODEOWNERS
│ └───dependabot.yml
├───.vscode/
│ ├───extensions.json
│ └───settings.json
├───examples/
│ ├───<example_folder>/
│ │ ├───README.md
│ │ ├───_footer.md
│ │ ├───_header.md
│ │ ├───main.tf
│ │ └───variables.tf
│ ├───.terraform-docs.yml
│ └───README.md
├───modules/
│ └───README.md
├───tests/
│ └───README.md
├───.gitignore
├───.terraform-docs.yml
├───CODE_OF_CONDUCT.md
├───LICENSE
├───Makefile
├───README.md
├───SECURITY.md
├───SUPPORT.md
├───_footer.md
├───_header.md
├───avm
├───avm.bat
├───locals.tf
├───main.privateendpoint.tf
├───main.telemetry.tf
├───main.tf
├───outputs.tf
├───terraform.tf
└───variables.tf
Code Styling
This section points to conventions to be followed when developing a module.
Casing
Use snake_casing
as per TFNFR3.
Input Parameters and Variables
Make sure to review all specifications of Category: Inputs/Outputs
within the Terraform specification pages.
Resources
Resources are primarily leveraged by resource modules to declare the primary resource of the main resource type deployed by the AVM module.
Make sure to review all specifications covering resource properties and usage.
Outputs
Make sure to review all specifications of Category: Inputs/Outputs
within the Terraform specification pages.
Interfaces
Note
This section is only relevant for contributions to resource modules.
To meet RMFR4 and RMFR5 AVM resource modules must leverage consistent interfaces for all the optional features/extension resources supported by the AVM module primary resource.
Please refer to the Terraform Interfaces page.
Telemetry
To meet the requirements of SFR3 & SFR4, we use the modtm telemetry provider. This lightweight telemetry provider sends telemetry data to Azure Application Insights via a HTTP POST front end service.
The modtm
telemetry provider is included in all Terraform modules and is enabled by default through the main.telemetry.tf file being automatically distributed from the template repo. You do not need to change this configuration.
Make sure that the modtm
provider is listed under the required_providers
section in the module’s terraform.tf
file using the following entry. This is also validated by the linter.
terraform {
required_providers {
# .. other required providers as needed
modtm = {
source = "Azure/modtm"
version = "~> 0.3"
}
}
}
Eventual Consistency
When creating modules, it is important to understand that the Azure Resource Manager (ARM) API is sometimes eventually consistent.
This means that when you create a resource, it may not be available immediately.
A good example of this is data plane role assignments.
When you create such a role assignment, it may take some time for the role assignment to be available.
We can use an optional time_sleep
resource to wait for the role assignment to be available before creating resources that depend on it.
# In variables.tf...
variable "wait_for_rbac_before_foo_operations" {
type: object({
create = optional(string, "30s")
destroy = optional(string, "0s")
})
default = {}
description = <<DESCRIPTION
This variable controls the amount of time to wait before performing foo operations.
It only applies when `var.role_assignments` and `var.foo` are both set.
This is useful when you are creating role assignments on the bar resource and immediately creating foo resources in it.
The default is 30 seconds for create and 0 seconds for destroy.
DESCRIPTION
}
# In main.tf...
resource "time_sleep" "wait_for_rbac_before_foo_operations" {
count = length(var.role_assignments) > 0 && length(var.foo) > 0 ? 1 : 0
depends_on = [
azurerm_role_assignment.this
]
create_duration = var.wait_for_rbac_before_foo_operations.create
destroy_duration = var.wait_for_rbac_before_foo_operations.destroy
# This ensures that the sleep is re-created when the role assignments change.
triggers = {
role_assignments = jsonencode(var.role_assignments)
}
}
resource "azurerm_foo" "this" {
for_each = var.foo
depends_on = [
time_sleep.wait_for_rbac_before_foo_operations
]
# ...
}