Skip to content

Commit 005e9cc

Browse files
authored
feat: Add support to create multiple dns zones and ability to set multiple dns records for each of the dns zones (#1073)
* feat: add support for multiple dns zones * fix validation * update validation * update error message
1 parent 9b2a3af commit 005e9cc

File tree

8 files changed

+114
-82
lines changed

8 files changed

+114
-82
lines changed

README.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,10 +214,8 @@ To attach access management tags to resources in this module, you need the follo
214214
| <a name="input_dns_instance_name"></a> [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 |
215215
| <a name="input_dns_location"></a> [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 |
216216
| <a name="input_dns_plan"></a> [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 |
217-
| <a name="input_dns_records"></a> [dns\_records](#input\_dns\_records) | List of DNS records to be created. | <pre>list(object({<br/> name = string<br/> type = string<br/> ttl = number<br/> rdata = string<br/> preference = optional(number, null)<br/> service = optional(string, null)<br/> protocol = optional(string, null)<br/> priority = optional(number, null)<br/> weight = optional(number, null)<br/> port = optional(number, null)<br/> }))</pre> | `[]` | no |
218-
| <a name="input_dns_zone_description"></a> [dns\_zone\_description](#input\_dns\_zone\_description) | The description of the DNS zone. | `string` | `"Default DNS Zone"` | no |
219-
| <a name="input_dns_zone_label"></a> [dns\_zone\_label](#input\_dns\_zone\_label) | Label associated with the DNS zone. | `string` | `"dns-zone"` | no |
220-
| <a name="input_dns_zone_name"></a> [dns\_zone\_name](#input\_dns\_zone\_name) | The name of the DNS zone to be created. | `string` | `null` | no |
217+
| <a name="input_dns_records"></a> [dns\_records](#input\_dns\_records) | List of DNS records to be created. | <pre>map(list(object({<br/> name = string<br/> type = string<br/> ttl = number<br/> rdata = string<br/> preference = optional(number, null)<br/> service = optional(string, null)<br/> protocol = optional(string, null)<br/> priority = optional(number, null)<br/> weight = optional(number, null)<br/> port = optional(number, null)<br/> })))</pre> | `{}` | no |
218+
| <a name="input_dns_zones"></a> [dns\_zones](#input\_dns\_zones) | List of the DNS zone to be created. | <pre>list(object({<br/> name = string<br/> description = optional(string)<br/> label = optional(string, "dns-zone")<br/> }))</pre> | `[]` | no |
221219
| <a name="input_enable_hub"></a> [enable\_hub](#input\_enable\_hub) | Indicates whether this VPC is enabled as a DNS name resolution hub. | `bool` | `false` | no |
222220
| <a name="input_enable_hub_vpc_crn"></a> [enable\_hub\_vpc\_crn](#input\_enable\_hub\_vpc\_crn) | Indicates whether Hub VPC CRN is passed. | `bool` | `false` | no |
223221
| <a name="input_enable_hub_vpc_id"></a> [enable\_hub\_vpc\_id](#input\_enable\_hub\_vpc\_id) | Indicates whether Hub VPC ID is passed. | `bool` | `false` | no |

examples/hub-spoke-delegated-resolver/main.tf

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,11 @@ module "hub_vpc" {
3636
prefix = "${var.prefix}-hub"
3737
tags = var.resource_tags
3838
enable_hub = true
39-
dns_zone_name = "hnsexample.com"
39+
dns_zones = [
40+
{
41+
name = "hnsexample.com"
42+
}
43+
]
4044
subnets = {
4145
zone-1 = [
4246
{

examples/vpc-with-dns/main.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ module "slz_vpc" {
4646
prefix = var.prefix
4747
tags = var.resource_tags
4848
enable_hub = true
49-
dns_zone_name = var.dns_zone_name
49+
dns_zones = var.dns_zones
5050
dns_records = var.dns_records
5151
subnets = local.subnets
5252
}

examples/vpc-with-dns/variables.tf

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ variable "resource_tags" {
3636

3737
variable "dns_records" {
3838
description = "List of DNS records to create"
39-
type = list(object({
39+
type = map(list(object({
4040
name = string
4141
type = string
4242
rdata = string
@@ -47,8 +47,8 @@ variable "dns_records" {
4747
protocol = optional(string)
4848
service = optional(string)
4949
weight = optional(number)
50-
}))
51-
default = [
50+
})))
51+
default = { "dns-example.com" = [
5252
{
5353
name = "testA"
5454
type = "A"
@@ -77,11 +77,21 @@ variable "dns_records" {
7777
rdata = "textinformation"
7878
ttl = 900
7979
}
80-
]
80+
]
81+
}
8182
}
8283

83-
variable "dns_zone_name" {
84-
description = "The name of the DNS zone to be created."
85-
type = string
86-
default = "dns-example.com"
84+
variable "dns_zones" {
85+
description = "The DNS zones to be created."
86+
type = list(object({
87+
name = string
88+
description = optional(string)
89+
label = optional(string, "dns-zone")
90+
}))
91+
default = [
92+
{
93+
name = "dns-example.com"
94+
description = "Example DNS zone"
95+
}
96+
]
8797
}

main.tf

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -361,21 +361,21 @@ resource "ibm_is_flow_log" "flow_logs" {
361361
###############################################################################
362362

363363
resource "ibm_dns_zone" "dns_zone" {
364-
count = var.enable_hub && !var.skip_custom_resolver_hub_creation && alltrue([var.dns_zone_name != null, var.dns_zone_name != ""]) ? 1 : 0
365-
name = var.dns_zone_name
364+
for_each = var.enable_hub && !var.skip_custom_resolver_hub_creation ? { for zone in var.dns_zones : zone.name => zone } : {}
365+
name = each.key
366366
instance_id = var.use_existing_dns_instance ? var.existing_dns_instance_id : ibm_resource_instance.dns_instance_hub[0].guid
367-
description = var.dns_zone_description
368-
label = var.dns_zone_label
367+
description = each.value.description == null ? "Hosted zone for ${each.key}" : each.value.description
368+
label = each.value.label
369369
}
370370

371371
##############################################################################
372372
# DNS PERMITTED NETWORK
373373
##############################################################################
374374

375375
resource "ibm_dns_permitted_network" "dns_permitted_network" {
376-
count = var.enable_hub && !var.skip_custom_resolver_hub_creation ? 1 : 0
376+
for_each = var.enable_hub && !var.skip_custom_resolver_hub_creation ? ibm_dns_zone.dns_zone : {}
377377
instance_id = var.use_existing_dns_instance ? var.existing_dns_instance_id : ibm_resource_instance.dns_instance_hub[0].guid
378-
zone_id = ibm_dns_zone.dns_zone[0].zone_id
378+
zone_id = each.value.zone_id
379379
vpc_crn = local.vpc_crn
380380
type = "vpc"
381381
}
@@ -384,10 +384,19 @@ resource "ibm_dns_permitted_network" "dns_permitted_network" {
384384
# DNS Records
385385
##############################################################################
386386

387+
locals {
388+
dns_records = flatten([
389+
for key, value in var.dns_records : [
390+
for idx, record in value : merge(record, { identifier = "${key}-${idx}", dns_zone = (key) })
391+
]
392+
])
393+
394+
}
395+
387396
resource "ibm_dns_resource_record" "dns_record" {
388-
for_each = length(ibm_dns_zone.dns_zone) > 0 ? { for idx, record in var.dns_records : idx => record } : {}
397+
for_each = length(ibm_dns_zone.dns_zone) > 0 ? { for record in local.dns_records : record.identifier => record } : {}
389398
instance_id = var.use_existing_dns_instance ? var.existing_dns_instance_id : ibm_resource_instance.dns_instance_hub[0].guid
390-
zone_id = ibm_dns_zone.dns_zone[0].zone_id
399+
zone_id = ibm_dns_zone.dns_zone[each.value.dns_zone].zone_id
391400
name = each.value.name
392401
type = each.value.type
393402

@@ -407,7 +416,10 @@ resource "ibm_dns_resource_record" "dns_record" {
407416
}
408417

409418
locals {
410-
record_ids = [for record in ibm_dns_resource_record.dns_record : element(split("/", record.id), 2)]
419+
record_ids = {
420+
for k in distinct([for d in local.dns_records : d.dns_zone]) :
421+
k => [for d in local.dns_records : element(split("/", ibm_dns_resource_record.dns_record[d.identifier].id), 2) if d.dns_zone == k]
422+
}
411423
}
412424

413425
##############################################################################

outputs.tf

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,17 +180,17 @@ output "dns_custom_resolver_id" {
180180
## DNS Zone and Records
181181
output "dns_zone_state" {
182182
description = "The state of the DNS zone."
183-
value = length(ibm_dns_zone.dns_zone) > 0 ? ibm_dns_zone.dns_zone[0].state : null
183+
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
184184
}
185185

186186
output "dns_zone_id" {
187187
description = "The ID of the DNS zone."
188-
value = length(ibm_dns_zone.dns_zone) > 0 ? ibm_dns_zone.dns_zone[0].zone_id : null
188+
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
189189
}
190190

191191
output "dns_zone" {
192192
description = "A map representing DNS zone information."
193-
value = length(ibm_dns_zone.dns_zone) > 0 ? ibm_dns_zone.dns_zone[0] : null
193+
value = length(ibm_dns_zone.dns_zone) > 0 ? [for zone in ibm_dns_zone.dns_zone : zone] : null
194194
}
195195

196196
output "dns_record_ids" {

tests/pr_test.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,19 @@ const terraformVersion = "terraform_v1.10" // This should match the version in t
4242
var permanentResources map[string]interface{}
4343

4444
// To verify DNS records creation
45-
var dnsRecordsMap = []map[string]interface{}{
46-
{"name": "testA", "type": "A", "rdata": "1.2.3.4", "ttl": 3600},
47-
{"name": "testAAAA", "type": "AAAA", "rdata": "2001:0db8:0012:0001:3c5e:7354:0000:5db5"},
48-
{"name": "testCNAME", "type": "CNAME", "rdata": "test.com"},
49-
{"name": "testTXT", "type": "TXT", "rdata": "textinformation", "ttl": 900},
50-
{"name": "testMX", "type": "MX", "rdata": "mailserver.test.com", "preference": 10},
51-
{"name": "testSRV", "type": "SRV", "rdata": "tester.com", "priority": 100, "weight": 100, "port": 8000, "service": "_sip", "protocol": "udp"},
45+
var dnsRecordsMap = map[string][]map[string]interface{}{
46+
"slz.com": {
47+
{"name": "testA", "type": "A", "rdata": "1.2.3.4", "ttl": 3600},
48+
{"name": "testAAAA", "type": "AAAA", "rdata": "2001:0db8:0012:0001:3c5e:7354:0000:5db5"},
49+
{"name": "testCNAME", "type": "CNAME", "rdata": "test.com"},
50+
{"name": "testTXT", "type": "TXT", "rdata": "textinformation", "ttl": 900},
51+
{"name": "testMX", "type": "MX", "rdata": "mailserver.test.com", "preference": 10},
52+
{"name": "testSRV", "type": "SRV", "rdata": "tester.com", "priority": 100, "weight": 100, "port": 8000, "service": "_sip", "protocol": "udp"},
53+
}}
54+
55+
// To verify DNS zone creation
56+
var dnsZoneMap = []map[string]interface{}{
57+
{"name": "slz.com"},
5258
}
5359

5460
func TestMain(m *testing.M) {
@@ -192,7 +198,7 @@ func TestRunVpcWithDnsExample(t *testing.T) {
192198

193199
options.TerraformVars["dns_records"] = dnsRecordsMap
194200
options.TerraformVars["name"] = "test-dns"
195-
options.TerraformVars["dns_zone_name"] = "slz.com"
201+
options.TerraformVars["dns_zones"] = dnsZoneMap
196202
output, err := options.RunTestConsistency()
197203
assert.Nil(t, err, "This should not have errored")
198204
assert.NotNil(t, output, "Expected some output")

variables.tf

Lines changed: 50 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -714,49 +714,44 @@ variable "dns_plan" {
714714
}
715715
}
716716

717-
variable "dns_zone_name" {
718-
description = "The name of the DNS zone to be created."
719-
default = null
720-
type = string
717+
variable "dns_zones" {
718+
description = "List of the DNS zone to be created."
719+
type = list(object({
720+
name = string
721+
description = optional(string)
722+
label = optional(string, "dns-zone")
723+
}))
724+
nullable = false
725+
default = []
721726

722727
validation {
723-
condition = var.enable_hub && !var.skip_custom_resolver_hub_creation ? alltrue([var.dns_zone_name != null, var.dns_zone_name != ""]) : true
724-
error_message = "dns_zone_name must not be null or empty when enable_hub is true and skip_custom_resolver_hub_creation is false."
728+
condition = var.enable_hub && !var.skip_custom_resolver_hub_creation ? length(var.dns_zones) != 0 : true
729+
error_message = "dns_zones must not be empty list when enable_hub is true and skip_custom_resolver_hub_creation is false."
725730
}
726731

727732
validation {
728-
condition = var.dns_zone_name == null ? true : !contains([
729-
"ibm.com",
730-
"softlayer.com",
731-
"bluemix.net",
732-
"softlayer.local",
733-
"mybluemix.net",
734-
"networklayer.com",
735-
"ibmcloud.com",
736-
"pdnsibm.net",
737-
"appdomain.cloud",
738-
"compass.cobaltiron.com"
739-
], var.dns_zone_name)
740-
733+
condition = alltrue([
734+
for zone in var.dns_zones :
735+
!contains([
736+
"ibm.com",
737+
"softlayer.com",
738+
"bluemix.net",
739+
"softlayer.local",
740+
"mybluemix.net",
741+
"networklayer.com",
742+
"ibmcloud.com",
743+
"pdnsibm.net",
744+
"appdomain.cloud",
745+
"compass.cobaltiron.com"
746+
], zone.name)
747+
])
741748
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)"
742749
}
743750
}
744751

745-
variable "dns_zone_description" {
746-
description = "The description of the DNS zone."
747-
type = string
748-
default = "Default DNS Zone"
749-
}
750-
751-
variable "dns_zone_label" {
752-
description = "Label associated with the DNS zone."
753-
type = string
754-
default = "dns-zone"
755-
}
756-
757752
variable "dns_records" {
758753
description = "List of DNS records to be created."
759-
type = list(object({
754+
type = map(list(object({
760755
name = string
761756
type = string
762757
ttl = number
@@ -767,30 +762,37 @@ variable "dns_records" {
767762
priority = optional(number, null)
768763
weight = optional(number, null)
769764
port = optional(number, null)
770-
}))
771-
default = []
765+
})))
766+
nullable = false
767+
default = {}
768+
769+
validation {
770+
condition = length(var.dns_records) == 0 || alltrue([for k in keys(var.dns_records) : contains([for zone in var.dns_zones : zone.name], k)])
771+
error_message = "The keys of 'dns_records' must match DNS names in 'dns_zones'."
772+
}
773+
772774
validation {
773-
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))])
774-
error_message = "Invalid domain resource record type is provided."
775+
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))]]))
776+
error_message = "Invalid domain resource record type is provided. Allowed values are 'A', 'AAAA', 'CNAME', 'MX', 'PTR', 'TXT', 'SRV'."
775777
}
776778

777779
validation {
778-
condition = length(var.dns_records) == 0 || alltrue([
779-
for record in var.dns_records == null ? [] : var.dns_records : (
780-
record.type != "SRV" || (
781-
record.protocol != null && record.port != null &&
782-
record.service != null && record.priority != null && record.weight != null
780+
condition = length(var.dns_records) == 0 || alltrue(flatten([
781+
for key, record in var.dns_records : [for value in record : (
782+
value.type != "SRV" || (
783+
value.protocol != null && value.port != null &&
784+
value.service != null && value.priority != null && value.weight != null
783785
)
784-
)
785-
])
786+
)
787+
]]))
786788
error_message = "Invalid SRV record configuration. For 'SRV' records, 'protocol' , 'service', 'priority', 'port' and 'weight' values must be provided."
787789
}
788790
validation {
789-
condition = length(var.dns_records) == 0 || alltrue([
790-
for record in var.dns_records == null ? [] : var.dns_records : (
791-
record.type != "MX" || record.preference != null
792-
)
793-
])
791+
condition = length(var.dns_records) == 0 || alltrue(flatten([
792+
for key, record in var.dns_records : [for value in record : (
793+
value.type != "MX" || value.preference != null
794+
)
795+
]]))
794796
error_message = "Invalid MX record configuration. For 'MX' records, value for 'preference' must be provided."
795797
}
796798
}

0 commit comments

Comments
 (0)