terraform-baseline

Terraform Baseline

Terraform baseline is:

Before using Terraform baseline, you should be familiar with the following pages from the official Terraform documentation:

Core principles

The best practices defined by the Terraform baseline are based on a set of core principles:

Usage

Version updates

Use Dependabot to keep modules you use updated to the latest versions.

Create a Dependabot configuration file .github/dependabot.yml in your repository containing the following configuration:

version: 2
updates:
  - package-ecosystem: terraform
    directories: [/terraform/**/*]
    groups:
      terraform:
        patterns: ["*"]

Repository

!!! note Azure CLI uses inconsistent separation of words in group names. We choose to consistenly separate words by - in module names.

Resources

Roles and scope

Hidden resources

Modules

Submodules

If a resource is a child of another resource:

For example, the SQL database resource is a child of the SQL server resource (a SQL database cannot exist without a SQL server):

Control plane and data plane

Conditional resources

Conditional resources refers to the creation of 0 or 1 resources based on a condition.

Use the count meta-argument to conditionally create resources based on a static value, for example a local or variable of type string or bool.

Using a variable of type string string is the more extensible approach, as you can add more allowed values down the road:

variable "kind" {
  description = "The kind of Web App to create. Allowed values are \"Linux\" and \"Windows\"."
  type        = string
  default     = "Linux"

  validation {
    condition     = contains(["Linux", "Windows"], var.kind)
    error_message = "Kind must be \"Linux\" or \"Windows\"."
  }
}

resource "azurerm_linux_web_app" "this" {
  count = var.kind == "Linux" ? 1 : 0
}

resource "azurerm_windows_web_app" "this" {
  count = var.kind == "Windows" ? 1 : 0
}

Repeatable resources

Repeatable resources refers to the creation of 0 or more resources based on a value.

For repeatable resources, use a variable of type map(object()) to dynamically create the resources, where setting the value to {} will not create any resources.

variable "firewall_rules" {
  description = "A map of SQL firewall rules to create."

  type = map(object({
    name             = string
    start_ip_address = string
    end_ip_address   = string
  }))

  default = {}
}

resource "azurerm_mssql_firewall_rule" "this" {
  for_each = var.firewall_rules

  name             = each.value.name
  start_ip_address = each.value.start_ip_address
  end_ip_address   = each.value.end_ip_address
}

Repeatable nested blocks

Repeatable nested blocks refers to the creation of 0 or more dynamic blocks based on a value.

For repeatable nested blocks, use a variable of type list(object()) to dynamically create the nested blocks, where setting the value to [] will not create any nested blocks:

variable "auth_settings_active_directory" {
  description = "A list of authentication settings using the Active Directory provider to configure for this Linux web app."

  type = list(object({
    client_id                  = string
    client_secret_setting_name = string
  }))

  default = []
}

resource "azurerm_linux_web_app" "this" {
  # omitted

  auth_settings {
    enabled = length(var.auth_settings_active_directory) == 0 ? false : true

    dynamic "active_directory" {
      for_each = var.auth_settings_active_directory

      content {
        client_id                  = active_directory.value["client_id"]
        client_secret_setting_name = active_directory.value["client_secret_setting_name"]
      }
    }
  }
}

Non-repeatable nested blocks

Non-repeatable nested blocks refers to the creation of 0 or 1 dynamic blocks based on a value.

For non-repeatable nested blocks, use a variable of type object() to dynamically create the nested block, where setting the value to null will not create the nested block:

variable "blob_properties" {
  description = "The blob properties for this storage account."

  type = object({
    versioning_enabled  = optional(bool, true)
    change_feed_enabled = optional(bool, true)
  })

  default = {}
}

resource "azurerm_storage_account" "this" {
  # omitted

  dynamic "blob_properties" {
    for_each = var.blob_properties != null ? [var.blob_properties] : []

    content {
      versioning_enabled  = blob_properties.value["versioning_enabled"]
      change_feed_enabled = blob_properties.value["change_feed_enabled"]
    }
  }
}

!!! abstract “Rationale” A nested block may not be supported in certain scenarios. For example, the blob_properties nested block for the azurerm_storage_account resource is only supported if the value of the account_kind argument is set to StorageV2 or BlobStorage.

!!! info “Exceptions” - Blocks that are defined as required by the provider (e.g. the site_config block for the azurerm_linux_web_app resource). - Blocks that are optional but requires an argument to enable/disable its functionality (e.g. the auth_settings block for the azurerm_linux_web_app resource which requires an argument enabled).

Variables and outputs

General

Variable and output names

Variable and output descriptions

Variable and output types

Meta-arguments

Lifecycle

Testing