From 8bacd8f5d5b7babae87d65bb5892d02a5383bb43 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Tue, 25 Oct 2022 13:38:46 +0200 Subject: [PATCH] Add support for DNS routing policies --- modules/dns/README.md | 56 ++++++++++++++++++----- modules/dns/main.tf | 98 +++++++++++++++++++++++++++++++++++++++- modules/dns/variables.tf | 26 +++++++++-- 3 files changed, 165 insertions(+), 15 deletions(-) diff --git a/modules/dns/README.md b/modules/dns/README.md index ebd200ab..c9232371 100644 --- a/modules/dns/README.md +++ b/modules/dns/README.md @@ -53,25 +53,59 @@ module "private-dns" { } # tftest modules=1 resources=1 ``` + +### Routing Policies + +```hcl +module "private-dns" { + source = "./fabric/modules/dns" + project_id = "myproject" + type = "private" + name = "test-example" + domain = "test.example." + client_networks = [var.vpc.self_link] + recordsets = { + "A regular" = { records = ["10.20.0.1"] } + "A geo" = { + geo_routing = [ + { location = "europe-west1", records = ["10.0.0.1"] }, + { location = "europe-west2", records = ["10.0.0.2"] }, + { location = "europe-west3", records = ["10.0.0.3"] } + ] + } + + "A wrr" = { + ttl = 600 + wrr_routing = [ + { weight = 0.6, records = ["10.10.0.1"] }, + { weight = 0.2, records = ["10.10.0.2"] }, + { weight = 0.2, records = ["10.10.0.3"] } + ] + } + } +} +# tftest modules=1 resources=4 +``` + ## Variables | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [domain](variables.tf#L51) | Zone domain, must end with a period. | string | ✓ | | -| [name](variables.tf#L69) | Zone name, must be unique within the project. | string | ✓ | | -| [project_id](variables.tf#L80) | Project id for the zone. | string | ✓ | | +| [domain](variables.tf#L53) | Zone domain, must end with a period. | string | ✓ | | +| [name](variables.tf#L71) | Zone name, must be unique within the project. | string | ✓ | | +| [project_id](variables.tf#L82) | Project id for the zone. | string | ✓ | | | [client_networks](variables.tf#L21) | List of VPC self links that can see this zone. | list(string) | | [] | | [description](variables.tf#L28) | Domain description. | string | | "Terraform managed." | -| [dnssec_config](variables.tf#L34) | DNSSEC configuration for this zone. | object({…}) | | null | -| [enable_logging](variables.tf#L62) | Enable query logging for this zone. Only valid for public zones. | bool | | false | -| [forwarders](variables.tf#L56) | Map of {IPV4_ADDRESS => FORWARDING_PATH} for 'forwarding' zone types. Path can be 'default', 'private', or null for provider default. | map(string) | | {} | -| [peer_network](variables.tf#L74) | Peering network self link, only valid for 'peering' zone types. | string | | null | -| [recordsets](variables.tf#L85) | Map of DNS recordsets in \"type name\" => {ttl, [records]} format. | map(object({…})) | | {} | -| [service_directory_namespace](variables.tf#L102) | Service directory namespace id (URL), only valid for 'service-directory' zone types. | string | | null | -| [type](variables.tf#L108) | Type of zone to create, valid values are 'public', 'private', 'forwarding', 'peering', 'service-directory'. | string | | "private" | -| [zone_create](variables.tf#L118) | Create zone. When set to false, uses a data source to reference existing zone. | bool | | true | +| [dnssec_config](variables.tf#L34) | DNSSEC configuration for this zone. | object({…}) | | {…} | +| [enable_logging](variables.tf#L64) | Enable query logging for this zone. Only valid for public zones. | bool | | false | +| [forwarders](variables.tf#L58) | Map of {IPV4_ADDRESS => FORWARDING_PATH} for 'forwarding' zone types. Path can be 'default', 'private', or null for provider default. | map(string) | | {} | +| [peer_network](variables.tf#L76) | Peering network self link, only valid for 'peering' zone types. | string | | null | +| [recordsets](variables.tf#L87) | Map of DNS recordsets in \"type name\" => {ttl, [records]} format. | map(object({…})) | | {} | +| [service_directory_namespace](variables.tf#L122) | Service directory namespace id (URL), only valid for 'service-directory' zone types. | string | | null | +| [type](variables.tf#L128) | Type of zone to create, valid values are 'public', 'private', 'forwarding', 'peering', 'service-directory'. | string | | "private" | +| [zone_create](variables.tf#L138) | Create zone. When set to false, uses a data source to reference existing zone. | bool | | true | ## Outputs diff --git a/modules/dns/main.tf b/modules/dns/main.tf index ed687d97..55b9301b 100644 --- a/modules/dns/main.tf +++ b/modules/dns/main.tf @@ -15,10 +15,25 @@ */ locals { - recordsets = { + _recordsets = { for key, attrs in var.recordsets : key => merge(attrs, zipmap(["type", "name"], split(" ", key))) } + geo_recordsets = { + for k, v in local._recordsets : + k => v + if v.geo_routing != null + } + recordsets = { + for k, v in local._recordsets : + k => v + if v.records != null + } + wrr_recordsets = { + for k, v in local._recordsets : + k => v + if v.wrr_routing != null + } zone = ( var.zone_create ? try( @@ -166,6 +181,87 @@ resource "google_dns_record_set" "cloud-static-records" { type = each.value.type ttl = each.value.ttl rrdatas = each.value.records + + depends_on = [ + google_dns_managed_zone.non-public, google_dns_managed_zone.public + ] +} + +resource "google_dns_record_set" "cloud-geo-records" { + for_each = ( + var.type == "public" || var.type == "private" + ? local.geo_recordsets + : {} + ) + project = var.project_id + managed_zone = var.name + name = ( + each.value.name == "" + ? var.domain + : ( + substr(each.value.name, -1, 1) == "." + ? each.value.name + : "${each.value.name}.${var.domain}" + ) + ) + type = each.value.type + ttl = each.value.ttl + + dynamic "routing_policy" { + for_each = each.value.geo_routing != null ? [1] : [0] + iterator = unused + content { + dynamic "geo" { + for_each = each.value.geo_routing + iterator = policy + content { + location = policy.value.location + rrdatas = policy.value.records + } + } + } + } + + depends_on = [ + google_dns_managed_zone.non-public, google_dns_managed_zone.public + ] +} + +resource "google_dns_record_set" "cloud-wrr-records" { + for_each = ( + var.type == "public" || var.type == "private" + ? local.wrr_recordsets + : {} + ) + project = var.project_id + managed_zone = var.name + name = ( + each.value.name == "" + ? var.domain + : ( + substr(each.value.name, -1, 1) == "." + ? each.value.name + : "${each.value.name}.${var.domain}" + ) + ) + type = each.value.type + ttl = each.value.ttl + + dynamic "routing_policy" { + for_each = each.value.wrr_routing != null ? [1] : [0] + iterator = unused + content { + dynamic "wrr" { + for_each = each.value.wrr_routing + iterator = policy + content { + weight = policy.value.weight + rrdatas = policy.value.records + } + } + } + } + depends_on = [ google_dns_managed_zone.non-public, google_dns_managed_zone.public ] diff --git a/modules/dns/variables.tf b/modules/dns/variables.tf index 749bbdd5..644b8395 100644 --- a/modules/dns/variables.tf +++ b/modules/dns/variables.tf @@ -45,7 +45,9 @@ variable "dnssec_config" { { algorithm = "rsasha256", key_length = 1024 } ) }) - default = null + default = { + state = "off" + } } variable "domain" { @@ -86,17 +88,35 @@ variable "recordsets" { description = "Map of DNS recordsets in \"type name\" => {ttl, [records]} format." type = map(object({ ttl = optional(number, 300) - records = list(string) + records = optional(list(string)) + geo_routing = optional(list(object({ + location = string + records = list(string) + }))) + wrr_routing = optional(list(object({ + weight = number + records = list(string) + }))) })) default = {} nullable = false validation { condition = alltrue([ - for k, v in var.recordsets == null ? {} : var.recordsets : + for k, v in coalesce(var.recordsets, {}) : length(split(" ", k)) == 2 ]) error_message = "Recordsets must have keys in the format \"type name\"." } + validation { + condition = alltrue([ + for k, v in coalesce(var.recordsets, {}) : ( + (v.records != null && v.wrr_routing == null && v.geo_routing == null) || + (v.records == null && v.wrr_routing != null && v.geo_routing == null) || + (v.records == null && v.wrr_routing == null && v.geo_routing != null) + ) + ]) + error_message = "Only one of records, wrr_routing or geo_routing can be defined for each recordset." + } } variable "service_directory_namespace" {