Deploy Azure DB with Terraform – Part 2

For this part of the series I want to talk a bit about the grammar and syntax of Terraform, how to reference existing resources, create new resources, and use variables.

Below is an example Terraform file using native syntax, which was designed to be human readable. You can get more details on all of this from the Terraform Documentation, but what I want to draw attention to here are the different block types like Terraform, Provider, Variable, Data, Locals, Resource, etc.

terraform {
  required_version = ">= 1.3.9, < 2.0.0"
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.40.0"
    }
  }
}
provider "azurerm" {
  features {}
}
variable "rg_name" {
  type        = string
  default     = "DBAtlas"
  description = "Name of the Resource group in which to deploy service objects"
}
variable "db_admin_login" {
  type        = string
  sensitive   = true
  description = "Admin User Name"
}
variable "db_admin_pwd" {
  type        = string
  sensitive   = true
  description = "Admin Password"
}
data "azurerm_resource_group" "dbatlas" {
  name = var.rg_name
}
locals {
  tags = {
    environment = "Production"
    repo        = "Terraform\\AzureDBExample"
    source      = "terraform"
    createdby   = "[email protected]"
  }
}
resource "azurerm_mssql_server" "exampleserver" {
  name                          = "a-clever-and-meaningful-server-name"
  resource_group_name           = data.azurerm_resource_group.dbatlas.name
  location                      = data.azurerm_resource_group.dbatlas.location
  version                       = "12.0"
  administrator_login           = var.db_admin_login
  administrator_login_password  = var.db_admin_pwd
  minimum_tls_version           = "1.2"
  public_network_access_enabled = true

  tags = merge(local.tags, {})
}
resource "azurerm_mssql_database" "exampledb" {
  name                           = "a-good-db-name"
  server_id                      = azurerm_mssql_server.exampleserver.id
  collation                      = "SQL_Latin1_General_CP1_CI_AS"
  license_type                   = "LicenseIncluded"
  max_size_gb                    = 1
  read_scale                     = false
  sku_name                       = "Basic"
  zone_redundant                 = false
  storage_account_type           = "Local"
  ledger_enabled                 = false

  tags = merge(local.tags, {})
}
Expand

The Terraform block can be used for things like requiring certain versions of Terraform, configuring a backend store your state file in, and specifying provider requirements.

terraform {
  required_version = ">= 1.3.7, < 2.0.0"
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.40.0"
    }
  }
}

The Provider block, this is used to configure each specific provider and also tells Terraform what to install so that they can be used. I think of providers like plugins that allow you to interact with different systems – in this case we are using the AzureRM provider, which allows us to manage Microsoft Azure using the Azure RM APIs via Terraform.

provider "azurerm" {
  features {}
}

The Variable block, or more succinctly input variables, allows you to specify parameters that make your code more composable and reusable.

variable "rg_name" {
  type        = string
  default     = "DBAtlas"
  description = "Name of the Resource group in which to deploy service objects"
}
variable "db_admin_login" {
  type        = string
  sensitive   = true
  description = "Admin User Name"
}
variable "db_admin_pwd" {
  type        = string
  sensitive   = true
  description = "Admin Password"
}

The Data block allows you to load or query data from the API for existing resources including things that have been defined outside of Terraform or defined by other Terraform configurations. In this case an already existing resource group.

data "azurerm_resource_group" "dbatlas" {
  name = var.rg_name
}

The Locals block is similar to the Variable block, generally these are often referenced values or expressions to help keep your code clean and orderly. In this case I am defining a base set of metadata tags I want to add to all my resources.

locals {
  tags = {
    environment = "Production"
    repo        = "Terraform\\AzureDBExample"
    source      = "terraform"
    createdby   = "[email protected]"
  }
}

Finally we get to the Resouce block, the main attraction. Each of these blocks describes 1 or more infrastructure objects that you want Terraform to deploy and manage. Resource(s) are the backbone of your Terraform and all the other blocks are playing a supporting role to this.

azuread_administrator {
    login_username              = "Azure SQL Admin"
    object_id                   = "1445b8e3-ec39-4ea1-920d-220553c4c267"
    azuread_authentication_only = false
  }

  tags = merge(local.tags, {})
}

resource "azurerm_mssql_database" "exampledb" {
  name                           = "a-good-db-name"
  server_id                      = azurerm_mssql_server.exampleserver.id
  collation                      = "SQL_Latin1_General_CP1_CI_AS"
  license_type                   = "LicenseIncluded"
  max_size_gb                    = 1
  read_scale                     = false
  sku_name                       = "Basic"
  zone_redundant                 = false
  storage_account_type           = "Local"
  ledger_enabled                 = false

  tags = merge(local.tags, {})
}

So hopefully that gives you a good primer and understanding concerning Terraform and its syntax, as well as how we can use it to deploy and manage resources. We did not discuss all the blocks, there are more such as Modules and Out Values, that you can explore as you get more proficient and comfortable with Terraform but are outside the scope of this blog post. Also I would like to note my preference, and those or most people using Terraform, is to separate many of these blocks out into there own files to assist in maintainability. You can of course put everything inside of one file, and there are many opinions on the ways to structure your Terraform projects but I will leave it up to the reader to do their own research on this and develop a practice that works best for them.

So in Part 1 we covered getting tooling installed and other configuration steps. In this part we went over understanding Terraform syntax. In the final part of this series I hope to go over actual examples of deploying resources to Azure using Terraform.