Feb 24 2022
Manage DNS Records with Terraform
Terraform helps building infrastructure as code. More and more hosting providers now offer Terraform plugins that let you handle DNS zones. Gandi is one of them along with OVH and many others. Let’s give it a try while version 2 has just ben released, and see how we can push DNS records to Gandi from a Git repository.
Gandi Terraform DNS Provider
Gandi Terraform provider has to be declared in a file usually called provider.tf. You can also squeeze in there Gandi API key that’s needed to authenticate and push new resources. It can be generated from their website in “User settings”, “Manage the user account and security settings” under the Security tab.
For obvious security reasons, you’re better off declaring the API key in the GANDI_KEY environment variable instead. I leave it in provider.tf for test purpose.
terraform {
required_providers {
gandi = {
version = "~> 2.0"
source = "go-gandi/gandi"
}
}
}
provider "gandi" {
key = "XXXXXXXXXXXXXXXXXXX"
}
DNS Record File
Gandi make automated backup of DNS records in a basic file format, in which I drew inspiration for my DNS record file. Let’s call it domain.txt where “domain” is literally your domain name. Here is a sample file with 3 DNS records.
www1 300 IN A 127.0.0.1
www2 300 IN A 127.0.0.1
www 300 IN CNAME www1
We need to declare the domain as a variable that we’ll use next, in variable.tf.
variable domain {
default = "mydomain.com"
}
DNS Records Terraform Code
Here’s a minimalist Terraform file main.tf that will read the file with DNS entries, and create a new record from each line.
data "local_file" "dns_file" {
filename = "./${var.domain}.txt"
}
locals {
# Convert file to array
dns_records = toset([ for e in split("\n", data.local_file.dns_file.content) :
replace(e, "/\\s+/", " ") if e != "" ])
}
resource "gandi_livedns_record" "record" {
for_each = local.dns_records
zone = var.domain
name = split(" ",each.value)[0]
type = split(" ",each.value)[3]
ttl = split(" ",each.value)[1]
values = [split(" ",each.value)[4]]
}
There you can appreciate the power of Terraform that can do many things in just a few lines of code.
Note that I remove multiple spaces – replace(e, “/\\s+/”, ” “) – when splitting lines because the split function creates empty elements if it finds multiple spaces in a row. I also ignore empty lines. Then we loop throughout each line to create DNS records with a gandi_livedns_record resource type.
This works for A and CNAME records.
We could apply different treatments for each type of records creating separate arrays, or to ignore other types of DNS entries:
dns_records = [ for e in split("\n", data.local_file.dns_file.content) :
replace(e, "/\\s+/", " ") if e != "" ]
A_records = toset([for e in local.dns_records : e if split(" ",e)[3] == "A"])
CNAME_records = toset([for e in local.dns_records : e if split(" ",e)[3] == "CNAME"])
You could then loop on A_records to create A records, and deal with CNAME in a second resource block.
We could also handle CNAME entries with multiple values, TXT records, etc …
Plan and Apply DNS Records Creation with Terraform
Run terraform plan to check what terraform is going to do. It should say it will create 3 resources. terraform apply will actually create DNS entries.
$ terraform plan
Terraform used the selected providers to generate the following execution plan.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# gandi_livedns_record.record["www 300 IN CNAME www1"] will be created
+ resource "gandi_livedns_record" "record" {
+ href = (known after apply)
+ id = (known after apply)
+ name = "www"
+ ttl = 300
+ type = "CNAME"
+ values = [
+ "www1",
]
+ zone = "domain.com"
}
[...]
Plan: 3 to add, 0 to change, 0 to destroy.
Performance wise, I tried to create 200 entries, and it took 2’57” on a pretty bad connection. Yes, that’s a bit slow since it relies on a REST API call for each one of them. The creation of an additional entry will be fast however.
A terraform plan took 1’31” to refresh these 200 entries.
Indeed, you can always skip the refresh step with terraform plan -refresh=false which takes less than a second.
The plan can be saved into a temporary file, which can be loaded when applying the new changes.
$ terraform plan -refresh=false -out=plan.file
$ terraform apply plan.file