diff --git a/README.md b/README.md index a481c24a..dc843a3f 100644 --- a/README.md +++ b/README.md @@ -214,10 +214,8 @@ To attach access management tags to resources in this module, you need the follo | [dns\_instance\_name](#input\_dns\_instance\_name) | The name to give the provisioned DNS instance. If not set, the module generates a name based on the `prefix` and `name` variables. | `string` | `null` | no | | [dns\_location](#input\_dns\_location) | The target location or environment for the DNS instance created to host the custom resolver in a hub-spoke DNS resolution topology. Only used if enable\_hub is true and skip\_custom\_resolver\_hub\_creation is false (defaults). | `string` | `"global"` | no | | [dns\_plan](#input\_dns\_plan) | The plan for the DNS resource instance created to host the custom resolver in a hub-spoke DNS resolution topology. Only used if enable\_hub is true and skip\_custom\_resolver\_hub\_creation is false (defaults). | `string` | `"standard-dns"` | no | -| [dns\_records](#input\_dns\_records) | List of DNS records to be created. |
list(object({
name = string
type = string
ttl = number
rdata = string
preference = optional(number, null)
service = optional(string, null)
protocol = optional(string, null)
priority = optional(number, null)
weight = optional(number, null)
port = optional(number, null)
}))
| `[]` | no | -| [dns\_zone\_description](#input\_dns\_zone\_description) | The description of the DNS zone. | `string` | `"Default DNS Zone"` | no | -| [dns\_zone\_label](#input\_dns\_zone\_label) | Label associated with the DNS zone. | `string` | `"dns-zone"` | no | -| [dns\_zone\_name](#input\_dns\_zone\_name) | The name of the DNS zone to be created. | `string` | `null` | no | +| [dns\_records](#input\_dns\_records) | List of DNS records to be created. |
map(list(object({
name = string
type = string
ttl = number
rdata = string
preference = optional(number, null)
service = optional(string, null)
protocol = optional(string, null)
priority = optional(number, null)
weight = optional(number, null)
port = optional(number, null)
})))
| `{}` | no | +| [dns\_zones](#input\_dns\_zones) | List of the DNS zone to be created. |
list(object({
name = string
description = optional(string)
label = optional(string, "dns-zone")
}))
| `[]` | no | | [enable\_hub](#input\_enable\_hub) | Indicates whether this VPC is enabled as a DNS name resolution hub. | `bool` | `false` | no | | [enable\_hub\_vpc\_crn](#input\_enable\_hub\_vpc\_crn) | Indicates whether Hub VPC CRN is passed. | `bool` | `false` | no | | [enable\_hub\_vpc\_id](#input\_enable\_hub\_vpc\_id) | Indicates whether Hub VPC ID is passed. | `bool` | `false` | no | diff --git a/examples/hub-spoke-delegated-resolver/main.tf b/examples/hub-spoke-delegated-resolver/main.tf index 7005039a..1ef8eecd 100644 --- a/examples/hub-spoke-delegated-resolver/main.tf +++ b/examples/hub-spoke-delegated-resolver/main.tf @@ -36,7 +36,11 @@ module "hub_vpc" { prefix = "${var.prefix}-hub" tags = var.resource_tags enable_hub = true - dns_zone_name = "hnsexample.com" + dns_zones = [ + { + name = "hnsexample.com" + } + ] subnets = { zone-1 = [ { diff --git a/examples/vpc-with-dns/main.tf b/examples/vpc-with-dns/main.tf index ce9ae17a..da867f48 100644 --- a/examples/vpc-with-dns/main.tf +++ b/examples/vpc-with-dns/main.tf @@ -46,7 +46,7 @@ module "slz_vpc" { prefix = var.prefix tags = var.resource_tags enable_hub = true - dns_zone_name = var.dns_zone_name + dns_zones = var.dns_zones dns_records = var.dns_records subnets = local.subnets } diff --git a/examples/vpc-with-dns/variables.tf b/examples/vpc-with-dns/variables.tf index 3861e898..bbc19994 100644 --- a/examples/vpc-with-dns/variables.tf +++ b/examples/vpc-with-dns/variables.tf @@ -36,7 +36,7 @@ variable "resource_tags" { variable "dns_records" { description = "List of DNS records to create" - type = list(object({ + type = map(list(object({ name = string type = string rdata = string @@ -47,8 +47,8 @@ variable "dns_records" { protocol = optional(string) service = optional(string) weight = optional(number) - })) - default = [ + }))) + default = { "dns-example.com" = [ { name = "testA" type = "A" @@ -77,11 +77,21 @@ variable "dns_records" { rdata = "textinformation" ttl = 900 } - ] + ] + } } -variable "dns_zone_name" { - description = "The name of the DNS zone to be created." - type = string - default = "dns-example.com" +variable "dns_zones" { + description = "The DNS zones to be created." + type = list(object({ + name = string + description = optional(string) + label = optional(string, "dns-zone") + })) + default = [ + { + name = "dns-example.com" + description = "Example DNS zone" + } + ] } diff --git a/main.tf b/main.tf index b48a4911..c9f21c0b 100644 --- a/main.tf +++ b/main.tf @@ -361,11 +361,11 @@ resource "ibm_is_flow_log" "flow_logs" { ############################################################################### resource "ibm_dns_zone" "dns_zone" { - count = var.enable_hub && !var.skip_custom_resolver_hub_creation && alltrue([var.dns_zone_name != null, var.dns_zone_name != ""]) ? 1 : 0 - name = var.dns_zone_name + for_each = var.enable_hub && !var.skip_custom_resolver_hub_creation ? { for zone in var.dns_zones : zone.name => zone } : {} + name = each.key instance_id = var.use_existing_dns_instance ? var.existing_dns_instance_id : ibm_resource_instance.dns_instance_hub[0].guid - description = var.dns_zone_description - label = var.dns_zone_label + description = each.value.description == null ? "Hosted zone for ${each.key}" : each.value.description + label = each.value.label } ############################################################################## @@ -373,9 +373,9 @@ resource "ibm_dns_zone" "dns_zone" { ############################################################################## resource "ibm_dns_permitted_network" "dns_permitted_network" { - count = var.enable_hub && !var.skip_custom_resolver_hub_creation ? 1 : 0 + for_each = var.enable_hub && !var.skip_custom_resolver_hub_creation ? ibm_dns_zone.dns_zone : {} instance_id = var.use_existing_dns_instance ? var.existing_dns_instance_id : ibm_resource_instance.dns_instance_hub[0].guid - zone_id = ibm_dns_zone.dns_zone[0].zone_id + zone_id = each.value.zone_id vpc_crn = local.vpc_crn type = "vpc" } @@ -384,10 +384,19 @@ resource "ibm_dns_permitted_network" "dns_permitted_network" { # DNS Records ############################################################################## +locals { + dns_records = flatten([ + for key, value in var.dns_records : [ + for idx, record in value : merge(record, { identifier = "${key}-${idx}", dns_zone = (key) }) + ] + ]) + +} + resource "ibm_dns_resource_record" "dns_record" { - for_each = length(ibm_dns_zone.dns_zone) > 0 ? { for idx, record in var.dns_records : idx => record } : {} + for_each = length(ibm_dns_zone.dns_zone) > 0 ? { for record in local.dns_records : record.identifier => record } : {} instance_id = var.use_existing_dns_instance ? var.existing_dns_instance_id : ibm_resource_instance.dns_instance_hub[0].guid - zone_id = ibm_dns_zone.dns_zone[0].zone_id + zone_id = ibm_dns_zone.dns_zone[each.value.dns_zone].zone_id name = each.value.name type = each.value.type @@ -407,7 +416,10 @@ resource "ibm_dns_resource_record" "dns_record" { } locals { - record_ids = [for record in ibm_dns_resource_record.dns_record : element(split("/", record.id), 2)] + record_ids = { + for k in distinct([for d in local.dns_records : d.dns_zone]) : + k => [for d in local.dns_records : element(split("/", ibm_dns_resource_record.dns_record[d.identifier].id), 2) if d.dns_zone == k] + } } ############################################################################## diff --git a/outputs.tf b/outputs.tf index 815e8af7..71d16eff 100644 --- a/outputs.tf +++ b/outputs.tf @@ -180,17 +180,17 @@ output "dns_custom_resolver_id" { ## DNS Zone and Records output "dns_zone_state" { description = "The state of the DNS zone." - value = length(ibm_dns_zone.dns_zone) > 0 ? ibm_dns_zone.dns_zone[0].state : null + value = length(ibm_dns_zone.dns_zone) > 0 ? [for zone in var.dns_zones : { (zone.name) = ibm_dns_zone.dns_zone[zone.name].state }] : null } output "dns_zone_id" { description = "The ID of the DNS zone." - value = length(ibm_dns_zone.dns_zone) > 0 ? ibm_dns_zone.dns_zone[0].zone_id : null + value = length(ibm_dns_zone.dns_zone) > 0 ? [for zone in var.dns_zones : { (zone.name) = ibm_dns_zone.dns_zone[zone.name].zone_id }] : null } output "dns_zone" { description = "A map representing DNS zone information." - value = length(ibm_dns_zone.dns_zone) > 0 ? ibm_dns_zone.dns_zone[0] : null + value = length(ibm_dns_zone.dns_zone) > 0 ? [for zone in ibm_dns_zone.dns_zone : zone] : null } output "dns_record_ids" { diff --git a/tests/pr_test.go b/tests/pr_test.go index b024fd86..174d5977 100644 --- a/tests/pr_test.go +++ b/tests/pr_test.go @@ -42,13 +42,19 @@ const terraformVersion = "terraform_v1.10" // This should match the version in t var permanentResources map[string]interface{} // To verify DNS records creation -var dnsRecordsMap = []map[string]interface{}{ - {"name": "testA", "type": "A", "rdata": "1.2.3.4", "ttl": 3600}, - {"name": "testAAAA", "type": "AAAA", "rdata": "2001:0db8:0012:0001:3c5e:7354:0000:5db5"}, - {"name": "testCNAME", "type": "CNAME", "rdata": "test.com"}, - {"name": "testTXT", "type": "TXT", "rdata": "textinformation", "ttl": 900}, - {"name": "testMX", "type": "MX", "rdata": "mailserver.test.com", "preference": 10}, - {"name": "testSRV", "type": "SRV", "rdata": "tester.com", "priority": 100, "weight": 100, "port": 8000, "service": "_sip", "protocol": "udp"}, +var dnsRecordsMap = map[string][]map[string]interface{}{ + "slz.com": { + {"name": "testA", "type": "A", "rdata": "1.2.3.4", "ttl": 3600}, + {"name": "testAAAA", "type": "AAAA", "rdata": "2001:0db8:0012:0001:3c5e:7354:0000:5db5"}, + {"name": "testCNAME", "type": "CNAME", "rdata": "test.com"}, + {"name": "testTXT", "type": "TXT", "rdata": "textinformation", "ttl": 900}, + {"name": "testMX", "type": "MX", "rdata": "mailserver.test.com", "preference": 10}, + {"name": "testSRV", "type": "SRV", "rdata": "tester.com", "priority": 100, "weight": 100, "port": 8000, "service": "_sip", "protocol": "udp"}, + }} + +// To verify DNS zone creation +var dnsZoneMap = []map[string]interface{}{ + {"name": "slz.com"}, } func TestMain(m *testing.M) { @@ -192,7 +198,7 @@ func TestRunVpcWithDnsExample(t *testing.T) { options.TerraformVars["dns_records"] = dnsRecordsMap options.TerraformVars["name"] = "test-dns" - options.TerraformVars["dns_zone_name"] = "slz.com" + options.TerraformVars["dns_zones"] = dnsZoneMap output, err := options.RunTestConsistency() assert.Nil(t, err, "This should not have errored") assert.NotNil(t, output, "Expected some output") diff --git a/variables.tf b/variables.tf index 4af7460c..3424ca75 100644 --- a/variables.tf +++ b/variables.tf @@ -714,49 +714,44 @@ variable "dns_plan" { } } -variable "dns_zone_name" { - description = "The name of the DNS zone to be created." - default = null - type = string +variable "dns_zones" { + description = "List of the DNS zone to be created." + type = list(object({ + name = string + description = optional(string) + label = optional(string, "dns-zone") + })) + nullable = false + default = [] validation { - condition = var.enable_hub && !var.skip_custom_resolver_hub_creation ? alltrue([var.dns_zone_name != null, var.dns_zone_name != ""]) : true - error_message = "dns_zone_name must not be null or empty when enable_hub is true and skip_custom_resolver_hub_creation is false." + condition = var.enable_hub && !var.skip_custom_resolver_hub_creation ? length(var.dns_zones) != 0 : true + error_message = "dns_zones must not be empty list when enable_hub is true and skip_custom_resolver_hub_creation is false." } validation { - condition = var.dns_zone_name == null ? true : !contains([ - "ibm.com", - "softlayer.com", - "bluemix.net", - "softlayer.local", - "mybluemix.net", - "networklayer.com", - "ibmcloud.com", - "pdnsibm.net", - "appdomain.cloud", - "compass.cobaltiron.com" - ], var.dns_zone_name) - + condition = alltrue([ + for zone in var.dns_zones : + !contains([ + "ibm.com", + "softlayer.com", + "bluemix.net", + "softlayer.local", + "mybluemix.net", + "networklayer.com", + "ibmcloud.com", + "pdnsibm.net", + "appdomain.cloud", + "compass.cobaltiron.com" + ], zone.name) + ]) error_message = "The specified DNS zone name is not permitted. Please choose a different domain name. [Learn more](https://cloud.ibm.com/docs/dns-svcs?topic=dns-svcs-managing-dns-zones&interface=ui#restricted-dns-zone-names)" } } -variable "dns_zone_description" { - description = "The description of the DNS zone." - type = string - default = "Default DNS Zone" -} - -variable "dns_zone_label" { - description = "Label associated with the DNS zone." - type = string - default = "dns-zone" -} - variable "dns_records" { description = "List of DNS records to be created." - type = list(object({ + type = map(list(object({ name = string type = string ttl = number @@ -767,30 +762,37 @@ variable "dns_records" { priority = optional(number, null) weight = optional(number, null) port = optional(number, null) - })) - default = [] + }))) + nullable = false + default = {} + + validation { + condition = length(var.dns_records) == 0 || alltrue([for k in keys(var.dns_records) : contains([for zone in var.dns_zones : zone.name], k)]) + error_message = "The keys of 'dns_records' must match DNS names in 'dns_zones'." + } + validation { - condition = length(var.dns_records) == 0 || alltrue([for record in var.dns_records != null ? var.dns_records : [] : (contains(["A", "AAAA", "CNAME", "MX", "PTR", "TXT", "SRV"], record.type))]) - error_message = "Invalid domain resource record type is provided." + condition = length(var.dns_records) == 0 || alltrue(flatten([for key, record in var.dns_records : [for value in record : (contains(["A", "AAAA", "CNAME", "MX", "PTR", "TXT", "SRV"], value.type))]])) + error_message = "Invalid domain resource record type is provided. Allowed values are 'A', 'AAAA', 'CNAME', 'MX', 'PTR', 'TXT', 'SRV'." } validation { - condition = length(var.dns_records) == 0 || alltrue([ - for record in var.dns_records == null ? [] : var.dns_records : ( - record.type != "SRV" || ( - record.protocol != null && record.port != null && - record.service != null && record.priority != null && record.weight != null + condition = length(var.dns_records) == 0 || alltrue(flatten([ + for key, record in var.dns_records : [for value in record : ( + value.type != "SRV" || ( + value.protocol != null && value.port != null && + value.service != null && value.priority != null && value.weight != null ) - ) - ]) + ) + ]])) error_message = "Invalid SRV record configuration. For 'SRV' records, 'protocol' , 'service', 'priority', 'port' and 'weight' values must be provided." } validation { - condition = length(var.dns_records) == 0 || alltrue([ - for record in var.dns_records == null ? [] : var.dns_records : ( - record.type != "MX" || record.preference != null - ) - ]) + condition = length(var.dns_records) == 0 || alltrue(flatten([ + for key, record in var.dns_records : [for value in record : ( + value.type != "MX" || value.preference != null + ) + ]])) error_message = "Invalid MX record configuration. For 'MX' records, value for 'preference' must be provided." } }