loading...
Cover image for Adding missing functionality to Terraform

Adding missing functionality to Terraform

booyaa profile image Mark Sta Ana Originally published at booyaa.wtf on ・3 min read

Photo by Hello I'm Nik 🇬🇧 on Unsplash

I needed to codify the creation of PostgreSQL read replicas, so I did a bit of research around ways I could do this quickly without diving into the Terraform provider.

The quickest way to do this was to:

  • use the local-exec provisioner to invoke the Azure CLI commands (details to follow)
  • wrap the code in a module (to allow for reuse and share with the community)

The Azure docs requires the following steps to be carried out to create a read replica using the Azure CLI are:

  • Enable replication support on the primary server
  • Restart the primary server (for the changes to take effect)
  • Create the replica using the primary server as the source

Caveat emptor: as the Terraform docs mention, provisioners are a last resort. A major downside of using this method to add missing functionality is that there's no state tracking, i.e. if you make a change to the resource, Terraform won't know about it.

Here's the essence of the code (I've omitted certain details for brevity the full code is on GitHub):

resource "null_resource" "postgresql-read-replica" {
  triggers = {
    resource_group_name            = var.resource_group_name
    postgresql_primary_server_name = var.postgresql_primary_server_name
    postgresql_replica_server_name = var.postgresql_replica_server_name
  }

The null_resource is also provisioner is used as a container for the local_exec calls. The triggers block allows the resource to be replaced, i.e. destroyed and recreated when the resource group, the PostgreSQL primary or replica server names changes.

You can already see that modules are just ordinary bits of Terraform code.

  provisioner "local-exec" {
    command = <<ENABLE_REPLICATION
az postgres server configuration set \
...
ENABLE_REPLICATION
  }

  provisioner "local-exec" {
    command = <<RESTART_SERVER
az postgres server restart \
...
RESTART_SERVER
  }

  provisioner "local-exec" {
    command = <<CREATE_REPLICA
az postgres server replica create \
...
CREATE_REPLICA
  }

These three provisioner blocks perform the required actions to create a read replica using the Azure CLI. To avoid having to escape quotes we're using the here doc notation.

  provisioner "local-exec" {
    when = "destroy"
    command = <<DESTROY_REPLICA
az postgres server delete \
  --name ${var.postgresql_replica_server_name} \
  --resource-group ${var.resource_group_name} \
  --yes
DESTROY_REPLICA
  }
}

Finally, we handle when what to do when the replica is destroyed.

I've uploaded the module to the Terraform registry which means the module can be easily referenced just like another resource:

module demo-replica {
  source                         = "booyaa/terraform-azurerm-postgresql-read-replica"
  resource_group_name            = azurerm_resource_group.demo.name
  postgresql_primary_server_name = azurerm_postgresql_server.demo.name
  postgresql_replica_server_name = "${azurerm_postgresql_server.demo.name}-replica"
}

Just like the Data Sources, modules can be used as a reference so we can now apply a firewall rule against the read replica:

resource "azurerm_postgresql_firewall_rule" "demo-replica" {
  name                = "office"
  resource_group_name = azurerm_resource_group.demo.name
  server_name         = module.demo-replica.replica_name
  start_ip_address    = "8.8.8.8"
  end_ip_address      = "8.8.8.8"

  depends_on = [module.demo-replica]
}

Posted on by:

booyaa profile

Mark Sta Ana

@booyaa

He/Him/Dat Boi. SRE for an Agribiz SaaS Startup. Terrorizing Azure with Terraform. Rusticating Windows.

Discussion

pic
Editor guide