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