Pozn.: Popis v článku vychází z Terraform verze 1.14.6, FortiOS Provider verze 1.24.1 a FortiGate s FortiOS 7.4.11.
Přidání záznamů na DNS server
Navazujeme na informace v předchozích článcích, které popisovaly použití Terraformu a připojení na FortiGate. V dnešním příkladu budeme řešit situaci, kdy na FortiGate provozujeme DNS server a chceme přidat nové DNS záznamy pomocí Terraform. Samozřejmě bychom jednoduše mohli provést celou konfiguraci DNS Database a vytvoření DNS Zone.
Problém je, že pro DNS záznamy neexistuje samostatný resource, ale konfigurují se jako vnořené bloky uvnitř nastavení celé zóny (DNS Database). Proto nemůžeme jednoduše přidávat nové objekty, ale musíme nejprve importovat existující konfiguraci.
Další problém je, že vnořené bloky jsou reprezentovány jako neuspořádaná skupina položek. Načítají se v náhodném pořadí. Když se porovnají s konfigurací, tak se navrhují změny (přepis položek). Řešíme to na konci článku seřazením. Ale pořád to není ideální, když třeba chceme odstranit jednu položku, tak dojde ke kompletní změně všech následujících.
Import existující konfigurace
Vytvoříme soubor import.tf. Pomocí bloku import budeme importovat prostředek typu fortios_system_dnsdatabase, jméno v konfiguraci použijeme dns. Načítáme existující zónu se jménem domain.tld.
import {
to = fortios_system_dnsdatabase.dns
id = "domain.tld"
}
Generování konfigurace
Pomocí Terraform CLI necháme nejprve vygenerovat konfigurační soubor.
PS D:\Terraform\FortiGate> terraform plan --generate-config-out=generated.tf
fortios_system_dnsdatabase.dns: Preparing import... [id=domain.tld]
fortios_system_dnsdatabase.dns: Refreshing state... [id=domain.tld]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
~ update in-place
Terraform will perform the following actions:
# fortios_system_dnsdatabase.dns will be updated in-place
# (imported from "domain.tld")
~ resource "fortios_system_dnsdatabase" "dns" {
allow_transfer = null
authoritative = "enable"
contact = "host"
domain = "domain.tld"
+ dynamic_sort_subtable = "false"
forwarder = null
forwarder6 = "::"
+ get_all_tables = "false"
id = "domain.tld"
...
dns_entry {
canonical_name = null
hostname = "server02"
id = 2
ip = "192.168.0.2"
ipv6 = "::"
preference = 10
status = "enable"
ttl = 0
type = "A"
}
...
Plan: 1 to import, 0 to add, 1 to change, 0 to destroy.
...
Co mi přijde zajímavé, že se mimo importu uvádí také změna. A jsou označeny argumenty dynamic_sort_subtable a get_all_tables jako přidané. Ve vygenerované konfiguraci jsou uvedeny jako
dynamic_sort_subtable = null get_all_tables = null
Import
Následně provedeme podle bloků import a resource import do stavového souboru.
PS D:\Terraform\FortiGate> terraform apply fortios_system_dnsdatabase.dns: Preparing import... [id=domain.tld] fortios_system_dnsdatabase.dns: Refreshing state... [id=domain.tld] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: ~ update in-place ... Plan: 1 to import, 0 to add, 1 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes fortios_system_dnsdatabase.dns: Importing... [id=domain.tld] fortios_system_dnsdatabase.dns: Import complete [id=domain.tld] fortios_system_dnsdatabase.dns: Modifying... [id=domain.tld] fortios_system_dnsdatabase.dns: Modifications complete after 0s [id=domain.tld] Apply complete! Resources: 1 imported, 0 added, 1 changed, 0 destroyed.
Úprava konfigurace
Můžeme rovnou pracovat se souborem generated.tf, ale pro větší přehlednost můžeme vytvořit nový soubor main.tf a do něj zkopírovat upravenou konfiguraci. Generovaná konfigurace obsahuje všechny argumenty daného prostředku (včetně výchozích hodnot atd.). Také můžeme odebrat soubor import.tf.
resource "fortios_system_dnsdatabase" "dns" {
name = "domain.tld"
domain = "domain.tld"
type = "primary"
view = "shadow"
ttl = 86400
authoritative = "enable"
dns_entry {
hostname = "server01"
id = 1
ip = "192.168.0.1"
type = "A"
}
dns_entry {
hostname = "server02"
id = 2
ip = "192.168.0.2"
type = "A"
}
dns_entry {
hostname = "server03"
id = 3
ip = "192.168.0.3"
type = "A"
}
}
Ověříme, že naše upravená konfigurace odpovídá reálnému stavu.
PS D:\Terraform\FortiGate> terraform plan fortios_system_dnsdatabase.dns: Refreshing state... [id=domain.tld] No changes. Your infrastructure matches the configuration. Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.
Vytvoření nového DNS záznamu
Da konfigurace přidáme nový blok dns_entry s údaji nového záznamu.
dns_entry {
hostname = "server04"
id = 4
ip = "192.168.0.4"
type = "A"
}
Aplikujeme změny konfigurace a záznam se vytvoří na FortiGate.

PS D:\Terraform\FortiGate> terraform apply
fortios_system_dnsdatabase.dns: Refreshing state... [id=domain.tld]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
~ update in-place
Terraform will perform the following actions:
# fortios_system_dnsdatabase.dns will be updated in-place
~ resource "fortios_system_dnsdatabase" "dns" {
id = "domain.tld"
name = "domain.tld"
# (23 unchanged attributes hidden)
+ dns_entry {
+ hostname = "server04"
+ id = 4
+ ip = "192.168.0.4"
+ type = "A"
}
# (3 unchanged blocks hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
fortios_system_dnsdatabase.dns: Modifying... [id=domain.tld]
fortios_system_dnsdatabase.dns: Modifications complete after 0s [id=domain.tld]
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.

Konfigurace s využitím proměnných
Konfiguraci můžeme ještě více upravit, abychom jednotlivé DNS záznamy definovali pomocí proměnné v samostatném souboru.
variables.tf
Mezi definice proměnných přidáme seznam objektů pro DNS záznamy.
# DNS Entries for DNS Zone
variable "dns_entries" {
description = "DNS resource records"
type = list(object({
hostname = string
id = number
ip = string
type = optional(string, "A")
})
)
}
dns-records.auto.tfvars
Vytvoříme nový soubor kam vložíme konfiguraci DNS záznamů.
dns_entries = [
{
hostname = "server01"
id = 1
ip = "192.168.0.1"
} , {
hostname = "server02"
id = 2
ip = "192.168.0.2"
} , {
hostname = "server03"
id = 3
ip = "192.168.0.3"
} , {
hostname = "server04"
id = 4
ip = "192.168.0.4"
}
]
main.tf
Upravíme konfiguraci prostředku a pro vnořené bloky dns_entry využijeme dynamic Blocks.
resource "fortios_system_dnsdatabase" "dns" {
name = "domain.tld"
domain = "domain.tld"
type = "primary"
view = "shadow"
ttl = 86400
authoritative = "enable"
dynamic_sort_subtable = "true"
dynamic "dns_entry" {
for_each = var.dns_entries
content {
hostname = dns_entry.value.hostname
id = dns_entry.value.id
ip = dns_entry.value.ip
type = dns_entry.value.type
}
}
}
Problém s pořadím položek
Resource fortios_system_dnsdatabase může obsahovat mnoho vnořených bloků dns_entry pro jednotlivé DNS záznamy. Podle informací v diskusi Ignoring order of resource blocks upon modification se využívá TypeSet, což je neuspořádaná skupina položek. Nepodporuje indexování podle klíče (ID). Tyto položky se z pohledu FortiAPI označují jako sub-tables.
V praxi se položky pokaždé načítají v jiném pořadí, což může vyvolávat opakovanou potřebu změny. Pro porovnání, zda došlo ke změně, se používá hash. Při testech s pár položkami se mi problém objevil pouze občas. Ale při stovce položek to bylo pokaždé.
Generování konfigurace a import
Již když generujeme konfiguraci, tak nejsou položky seřazené podle ID a když pak chceme provést import, tak se provádí aplikace změn.
PS D:\Terraform\FortiGate> terraform apply
...
Terraform will perform the following actions:
# fortios_system_dnsdatabase.dns will be updated in-place
# (imported from "domain.tld")
~ resource "fortios_system_dnsdatabase" "dns" {
allow_transfer = null
authoritative = "enable"
contact = "host"
domain = "domain.tld"
+ dynamic_sort_subtable = "false"
...
~ dns_entry {
canonical_name = null
~ hostname = "server01" -> "server03"
~ id = 1 -> 3
~ ip = "192.168.0.1" -> "192.168.0.3"
ipv6 = "::"
preference = 10
status = "enable"
ttl = 0
type = "A"
}
~ dns_entry {
...
Plan: 1 to import, 0 to add, 1 to change, 0 to destroy.
Když aplikujeme změny, tak se na FortiGate naštěstí nic nezmění. Dojde k importu vzdálených objektů do Terraform state.
dynamic_sort_subtable
Řešením by měl být argument dynamic_sort_subtable, který zařídí, že se položky načítají seřazené. Problém je, že vygenerovaná konfigurace seřazená není.
resource "fortios_system_dnsdatabase" "dns" {
dynamic_sort_subtable = "true"
Vytvoření seřazené konfigurace
Zkusil jsem dát dohromady alternativní řešení, kdy využijeme datový zdroj a načteme fortios_system_dnsdatabase. Seřadíme položky podle ID, které musíme nejprve převést na číslo. Pomocí output zobrazíme hodnoty jednotlivých DNS záznamů přesně ve formátu, jak jsme si připravili konfiguraci proměnné v minulé kapitole.
locals {
padded_map = {
for entry in data.fortios_system_dnsdatabase.DNS.dns_entry :
format("%010d", tonumber(entry.id)) => entry
}
dns_entries_sorted = [
for k in sort(keys(local.padded_map)) :
local.padded_map[k]
]
}
data "fortios_system_dnsdatabase" "DNS" {
name = "domain.tld"
}
output "dns_entries" {
value = [
for entry in local.dns_entries_sorted : {
id = entry.id
hostname = entry.hostname
ip = entry.ip
type = entry.type
}
]
}
Když aplikujeme změny, tak dojde k načtení hodnot a uložení do stavového souboru. Zároveň se zobrazí jednotlivé položky (výstup).
PS D:\Terraform\FortiGate> terraform apply
data.fortios_system_dnsdatabase.DNS: Reading...
fortios_system_dnsdatabase.dns: Refreshing state... [id=domain.tld]
data.fortios_system_dnsdatabase.DNS: Read complete after 0s [id=domain.tld]
Changes to Outputs:
+ dns_entries = [
+ {
+ hostname = "server01"
+ id = 1
+ ip = "192.168.0.1"
+ type = "A"
},
+ {
+ hostname = "server02"
+ id = 2
+ ip = "192.168.0.2"
+ type = "A"
},
+ {
+ hostname = "server03"
+ id = 3
+ ip = "192.168.0.3"
+ type = "A"
},
+ {
+ hostname = "server04"
+ id = 4
+ ip = "192.168.0.4"
+ type = "A"
},
]
You can apply this plan to save these new output values to the Terraform state, without changing any real infrastructure.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
dns_entries = [
{
"hostname" = "server01"
"id" = 1
"ip" = "192.168.0.1"
"type" = "A"
},
{
"hostname" = "server02"
"id" = 2
"ip" = "192.168.0.2"
"type" = "A"
},
{
"hostname" = "server03"
"id" = 3
"ip" = "192.168.0.3"
"type" = "A"
},
{
"hostname" = "server04"
"id" = 4
"ip" = "192.168.0.4"
"type" = "A"
},
]
Zobrazené položky (Outputs) zkopírujeme do souboru dns-records.auto.tfvars. Následně provedeme import.
Zatím zde nejsou žádné komentáře.