diff --git a/README.md b/README.md index 12478eb6..bfca1efc 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ This module creates the following IBM Cloud® Virtual Private Cloud (VPC) net * [Landing Zone example](./examples/landing_zone) * [No Prefix Example](./examples/no-prefix) * [Specific Zone Only Example](./examples/specific-zone-only) + * [VPC with DNS example](./examples/vpc-with-dns) * [Contributing](#contributing) @@ -150,6 +151,9 @@ To attach access management tags to resources in this module, you need the follo | Name | Type | |------|------| | [ibm_dns_custom_resolver.custom_resolver_hub](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/dns_custom_resolver) | resource | +| [ibm_dns_permitted_network.dns_permitted_network](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/dns_permitted_network) | resource | +| [ibm_dns_resource_record.dns_record](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/dns_resource_record) | resource | +| [ibm_dns_zone.dns_zone](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/dns_zone) | resource | | [ibm_iam_authorization_policy.policy](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/iam_authorization_policy) | resource | | [ibm_iam_authorization_policy.vpc_dns_resolution_auth_policy](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/iam_authorization_policy) | resource | | [ibm_is_flow_log.flow_logs](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_flow_log) | resource | @@ -191,6 +195,10 @@ 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` | `"slz.com"` | 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 |
@@ -235,6 +243,10 @@ To attach access management tags to resources in this module, you need the follo
| [dns\_endpoint\_gateways\_by\_crn](#output\_dns\_endpoint\_gateways\_by\_crn) | The list of VPEs that are made available for DNS resolution in the created VPC. Only set if enable\_hub is false and enable\_hub\_vpc\_id are true. |
| [dns\_endpoint\_gateways\_by\_id](#output\_dns\_endpoint\_gateways\_by\_id) | The list of VPEs that are made available for DNS resolution in the created VPC. Only set if enable\_hub is false and enable\_hub\_vpc\_id are true. |
| [dns\_instance\_id](#output\_dns\_instance\_id) | The ID of the DNS instance. |
+| [dns\_record\_ids](#output\_dns\_record\_ids) | List of all the domain resource records. |
+| [dns\_zone](#output\_dns\_zone) | A map representing DNS zone information. |
+| [dns\_zone\_id](#output\_dns\_zone\_id) | The ID of the DNS zone. |
+| [dns\_zone\_state](#output\_dns\_zone\_state) | The state of the DNS zone. |
| [network\_acls](#output\_network\_acls) | List of shortnames and IDs of network ACLs |
| [public\_gateways](#output\_public\_gateways) | Map of public gateways by zone |
| [subnet\_detail\_list](#output\_subnet\_detail\_list) | A list of subnets containing names, CIDR blocks, and zones. |
diff --git a/examples/no-prefix/README.md b/examples/no-prefix/README.md
index 715e0eaa..0047fe5d 100644
--- a/examples/no-prefix/README.md
+++ b/examples/no-prefix/README.md
@@ -7,4 +7,4 @@ The following resources are provisioned by this example:
* A new resource group, if an existing one is not passed in.
* An IBM Virtual Private Cloud (VPC).
* An IBM Cloud Object Storage Instance
-* An IBMM Cloud Storage Bucket
+* An IBM Cloud Storage Bucket
diff --git a/examples/vpc-with-dns/README.md b/examples/vpc-with-dns/README.md
new file mode 100644
index 00000000..8d515466
--- /dev/null
+++ b/examples/vpc-with-dns/README.md
@@ -0,0 +1,17 @@
+# VPC with DNS example
+
+A simple example demonstrating the provisioning of a `Secure Landing Zone (SLZ) Virtual Private Cloud (VPC)` across two zones (`Zone 1` and `Zone 2`). This setup includes the creation of `Domain Name System (DNS) Zones and Records`, linking the provisioned VPC as a permitted network for DNS operations.
+
+The following resources are provisioned by this example:
+
+* A new `resource group`, if an existing one is not passed in.
+
+* An IBM `Virtual Private Cloud (VPC)` with a publicly exposed subnet.
+
+* Private `DNS zone` which can only be resolved from IBM Cloud's private network.
+
+* `DNS permitted network` - [DNS Service](https://cloud.ibm.com/docs/dns-svcs/getting-started.html) is a global service, hence the permitted networks (for example, a `VPC`) should be added from any IBM Cloud region. This adds the network to the DNS zone, giving the network access to the zone. Maximum of 10 permitted networks can be added to a `DNS zone`. [Learn more](https://cloud.ibm.com/docs/dns-svcs?topic=dns-svcs-managing-permitted-networks&interface=ui)
+
+* `DNS Records` - `DNS Records` make the connection between human-readable names and IP addresses.
+
+> Note: To create a `PTR` type record, you must have an existing `A` or `AAAA` record that is not already associated with another `PTR` record. [Learn More](https://cloud.ibm.com/docs/dns-svcs?topic=dns-svcs-managing-dns-records&interface=ui#ptr-record)
diff --git a/examples/vpc-with-dns/main.tf b/examples/vpc-with-dns/main.tf
new file mode 100644
index 00000000..f039ba73
--- /dev/null
+++ b/examples/vpc-with-dns/main.tf
@@ -0,0 +1,52 @@
+##############################################################################
+# Resource Group
+##############################################################################
+
+module "resource_group" {
+ source = "terraform-ibm-modules/resource-group/ibm"
+ version = "1.1.6"
+ # if an existing resource group is not set (null) create a new one using prefix
+ resource_group_name = var.resource_group == null ? "${var.prefix}-resource-group" : null
+ existing_resource_group_name = var.resource_group
+}
+
+#############################################################################
+# Locals
+#############################################################################
+locals {
+ subnets = {
+ zone-1 = [
+ {
+ name = "subnet-a"
+ cidr = "10.10.10.0/24"
+ public_gateway = true
+ acl_name = "vpc-acl"
+ }
+ ],
+ zone-2 = [
+ {
+ name = "subnet-b"
+ cidr = "10.20.10.0/24"
+ public_gateway = false
+ acl_name = "vpc-acl"
+ }
+ ]
+ }
+}
+
+#############################################################################
+# Provision VPC
+#############################################################################
+
+module "slz_vpc" {
+ source = "../../"
+ resource_group_id = module.resource_group.resource_group_id
+ region = var.region
+ name = var.name
+ prefix = var.prefix
+ tags = var.resource_tags
+ enable_hub = true
+ dns_zone_name = var.dns_zone_name
+ dns_records = var.dns_records
+ subnets = local.subnets
+}
diff --git a/examples/vpc-with-dns/outputs.tf b/examples/vpc-with-dns/outputs.tf
new file mode 100644
index 00000000..61f18453
--- /dev/null
+++ b/examples/vpc-with-dns/outputs.tf
@@ -0,0 +1,52 @@
+##############################################################################
+# Outputs
+##############################################################################
+
+output "vpc_id" {
+ value = module.slz_vpc.vpc_id
+ description = "VPC id"
+}
+
+output "vpc_crn" {
+ value = module.slz_vpc.vpc_crn
+ description = "VPC crn"
+}
+
+output "network_acls" {
+ value = module.slz_vpc.network_acls
+ description = "VPC network ACLs"
+}
+
+output "public_gateways" {
+ value = module.slz_vpc.public_gateways
+ description = "VPC public gateways"
+}
+
+output "subnet_zone_list" {
+ value = module.slz_vpc.subnet_zone_list
+ description = "VPC subnet zone list"
+}
+
+output "subnet_detail_map" {
+ value = module.slz_vpc.subnet_detail_map
+ description = "VPC subnet detail map"
+}
+
+output "dns_zone_state" {
+ description = "The state of the DNS zone."
+ value = module.slz_vpc.dns_zone_state
+}
+
+output "dns_zone_id" {
+ description = "The ID of the DNS zone."
+ value = module.slz_vpc.dns_zone_id
+}
+output "dns_record_ids" {
+ description = "List of all the domain resource records."
+ value = module.slz_vpc.dns_record_ids
+}
+
+output "dns_zone" {
+ description = "A map representing DNS zone information."
+ value = module.slz_vpc.dns_zone
+}
diff --git a/examples/vpc-with-dns/provider.tf b/examples/vpc-with-dns/provider.tf
new file mode 100644
index 00000000..df45ef50
--- /dev/null
+++ b/examples/vpc-with-dns/provider.tf
@@ -0,0 +1,4 @@
+provider "ibm" {
+ ibmcloud_api_key = var.ibmcloud_api_key
+ region = var.region
+}
diff --git a/examples/vpc-with-dns/variables.tf b/examples/vpc-with-dns/variables.tf
new file mode 100644
index 00000000..3861e898
--- /dev/null
+++ b/examples/vpc-with-dns/variables.tf
@@ -0,0 +1,87 @@
+variable "ibmcloud_api_key" {
+ description = "APIkey that's associated with the account to provision resources."
+ type = string
+ sensitive = true
+}
+
+variable "region" {
+ description = "The region to which to deploy the VPC"
+ type = string
+ default = "us-south"
+}
+
+variable "prefix" {
+ description = "The prefix that you would like to append to your resources"
+ type = string
+ default = "dns"
+}
+
+variable "name" {
+ description = "The name of the vpc"
+ type = string
+ default = "slz-vpc"
+}
+
+variable "resource_group" {
+ type = string
+ description = "An existing resource group name to use for this example, if unset a new resource group will be created"
+ default = null
+}
+
+variable "resource_tags" {
+ description = "List of Tags for the resource created"
+ type = list(string)
+ default = null
+}
+
+variable "dns_records" {
+ description = "List of DNS records to create"
+ type = list(object({
+ name = string
+ type = string
+ rdata = string
+ ttl = optional(number)
+ preference = optional(number)
+ priority = optional(number)
+ port = optional(number)
+ protocol = optional(string)
+ service = optional(string)
+ weight = optional(number)
+ }))
+ default = [
+ {
+ name = "testA"
+ type = "A"
+ rdata = "1.2.3.4"
+ ttl = 3600
+ },
+ {
+ name = "testMX"
+ type = "MX"
+ rdata = "mailserver.test.com"
+ preference = 10
+ },
+ {
+ type = "SRV"
+ name = "testSRV"
+ rdata = "tester.com"
+ priority = 100
+ weight = 100
+ port = 8000
+ service = "_sip"
+ protocol = "udp"
+ },
+ {
+ name = "testTXT"
+ type = "TXT"
+ rdata = "textinformation"
+ ttl = 900
+ }
+ ]
+}
+
+variable "dns_zone_name" {
+ description = "The name of the DNS zone to be created."
+ type = string
+ default = "dns-example.com"
+}
diff --git a/examples/vpc-with-dns/version.tf b/examples/vpc-with-dns/version.tf
new file mode 100644
index 00000000..fa870dd3
--- /dev/null
+++ b/examples/vpc-with-dns/version.tf
@@ -0,0 +1,10 @@
+terraform {
+ required_version = ">= 1.3.0"
+ required_providers {
+ # Pin to the lowest provider version of the range defined in the main module's version.tf to ensure lowest version still works
+ ibm = {
+ source = "IBM-Cloud/ibm"
+ version = "1.59.0"
+ }
+ }
+}
diff --git a/main.tf b/main.tf
index efe70e1a..28a46692 100644
--- a/main.tf
+++ b/main.tf
@@ -355,3 +355,57 @@ resource "ibm_is_flow_log" "flow_logs" {
}
##############################################################################
+# DNS ZONE
+# ##############################################################################
+
+resource "ibm_dns_zone" "dns_zone" {
+ count = var.enable_hub && !var.skip_custom_resolver_hub_creation ? 1 : 0
+ name = var.dns_zone_name
+ 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
+}
+
+##############################################################################
+# DNS PERMITTED NETWORK
+##############################################################################
+
+resource "ibm_dns_permitted_network" "dns_permitted_network" {
+ count = var.enable_hub && !var.skip_custom_resolver_hub_creation ? 1 : 0
+ 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
+ vpc_crn = local.vpc_crn
+ type = "vpc"
+}
+
+##############################################################################
+# DNS Records
+##############################################################################
+
+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 } : {}
+ 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
+ name = each.value.name
+ type = each.value.type
+
+ # Default ttl is 15 minutes [Refer](https://cloud.ibm.com/docs/dns-svcs?topic=dns-svcs-managing-dns-records&interface=ui)
+ ttl = try(each.value.ttl, 900)
+ rdata = each.value.rdata
+
+ # SRV values
+ port = each.value.type == "SRV" ? each.value.port : null
+ priority = each.value.type == "SRV" ? each.value.priority : null
+ protocol = each.value.type == "SRV" ? each.value.protocol : null
+ service = each.value.type == "SRV" ? startswith(each.value.service, "_") ? each.value.service : "_${each.value.service}" : null
+ weight = each.value.type == "SRV" ? each.value.weight : null
+
+ # MX record
+ preference = each.value.type == "MX" ? each.value.preference : null
+}
+
+locals {
+ record_ids = [for record in ibm_dns_resource_record.dns_record : element(split("/", record.id), 2)]
+}
+
+##############################################################################
diff --git a/outputs.tf b/outputs.tf
index 5403ea26..a99031fa 100644
--- a/outputs.tf
+++ b/outputs.tf
@@ -176,3 +176,24 @@ output "dns_custom_resolver_id" {
description = "The ID of the DNS Custom Resolver."
value = (var.enable_hub && !var.skip_custom_resolver_hub_creation) ? one(ibm_dns_custom_resolver.custom_resolver_hub[*].instance_id) : null
}
+
+## 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
+}
+
+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
+}
+
+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
+}
+
+output "dns_record_ids" {
+ description = "List of all the domain resource records."
+ value = length(ibm_dns_resource_record.dns_record) > 0 ? local.record_ids : null
+}
diff --git a/tests/pr_test.go b/tests/pr_test.go
index 35fe1f95..242ddbdd 100644
--- a/tests/pr_test.go
+++ b/tests/pr_test.go
@@ -25,6 +25,7 @@ const hubAndSpokeDelegatedExampleTerraformDir = "examples/hub-spoke-delegated-re
const existingVPCExampleTerraformDir = "examples/existing_vpc"
const specificZoneExampleTerraformDir = "examples/specific-zone-only"
const noprefixExampleTerraformDir = "examples/no-prefix"
+const vpcWithDnsExampleTerraformDir = "examples/vpc-with-dns"
const resourceGroup = "geretain-test-resources"
// Define a struct with fields that match the structure of the YAML data
@@ -32,6 +33,16 @@ const yamlLocation = "../common-dev-assets/common-go-assets/common-permanent-res
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"},
+}
+
func TestMain(m *testing.M) {
// Read the YAML file contents
var err error
@@ -171,3 +182,23 @@ func TestRunExistingVPCExample(t *testing.T) {
logger.Log(t, "END: Destroy (existing resources)")
}
}
+
+func TestRunVpcWithDnsExample(t *testing.T) {
+ t.Parallel()
+
+ options := testhelper.TestOptionsDefaultWithVars(&testhelper.TestOptions{
+ Testing: t,
+ TerraformDir: vpcWithDnsExampleTerraformDir,
+ Prefix: "dns-slz",
+ ResourceGroup: resourceGroup,
+ Region: "us-south",
+ })
+ options.TerraformVars = map[string]interface{}{
+ "dns_records": dnsRecordsMap,
+ "name": "test-dns",
+ "dns_zone_name": "slz.com",
+ }
+ 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 ac7a8114..597f6673 100644
--- a/variables.tf
+++ b/variables.tf
@@ -637,3 +637,78 @@ variable "dns_plan" {
error_message = "`dns_plan` can either be standard-dns or free-plan."
}
}
+
+variable "dns_zone_name" {
+ description = "The name of the DNS zone to be created."
+ default = "slz.com"
+ type = string
+ validation {
+ condition = !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)
+
+ 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({
+ 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)
+ }))
+ default = []
+ 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."
+ }
+
+ 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
+ )
+ )
+ ])
+ 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
+ )
+ ])
+ error_message = "Invalid MX record configuration. For 'MX' records, value for 'preference' must be provided."
+ }
+}