Adding missing functionality to Terraform

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:

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

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 \

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

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

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} \

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            =
  postgresql_primary_server_name =
  postgresql_replica_server_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 =
  server_name         = module.demo-replica.replica_name
  start_ip_address    = ""
  end_ip_address      = ""

  depends_on = [module.demo-replica]