Merge pull request #916 from GoogleCloudPlatform/jccb/dns-routing
Add support for DNS routing policies
This commit is contained in:
commit
476050a6ec
|
@ -53,25 +53,58 @@ 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
|
||||
```
|
||||
<!-- BEGIN TFDOC -->
|
||||
|
||||
## Variables
|
||||
|
||||
| name | description | type | required | default |
|
||||
|---|---|:---:|:---:|:---:|
|
||||
| [domain](variables.tf#L51) | Zone domain, must end with a period. | <code>string</code> | ✓ | |
|
||||
| [name](variables.tf#L69) | Zone name, must be unique within the project. | <code>string</code> | ✓ | |
|
||||
| [project_id](variables.tf#L80) | Project id for the zone. | <code>string</code> | ✓ | |
|
||||
| [domain](variables.tf#L54) | Zone domain, must end with a period. | <code>string</code> | ✓ | |
|
||||
| [name](variables.tf#L72) | Zone name, must be unique within the project. | <code>string</code> | ✓ | |
|
||||
| [project_id](variables.tf#L83) | Project id for the zone. | <code>string</code> | ✓ | |
|
||||
| [client_networks](variables.tf#L21) | List of VPC self links that can see this zone. | <code>list(string)</code> | | <code>[]</code> |
|
||||
| [description](variables.tf#L28) | Domain description. | <code>string</code> | | <code>"Terraform managed."</code> |
|
||||
| [dnssec_config](variables.tf#L34) | DNSSEC configuration for this zone. | <code title="object({ non_existence = optional(string, "nsec3") state = string key_signing_key = optional(object( { algorithm = string, key_length = number }), { algorithm = "rsasha256", key_length = 2048 } ) zone_signing_key = optional(object( { algorithm = string, key_length = number }), { algorithm = "rsasha256", key_length = 1024 } ) })">object({…})</code> | | <code>null</code> |
|
||||
| [enable_logging](variables.tf#L62) | Enable query logging for this zone. Only valid for public zones. | <code>bool</code> | | <code>false</code> |
|
||||
| [forwarders](variables.tf#L56) | Map of {IPV4_ADDRESS => FORWARDING_PATH} for 'forwarding' zone types. Path can be 'default', 'private', or null for provider default. | <code>map(string)</code> | | <code>{}</code> |
|
||||
| [peer_network](variables.tf#L74) | Peering network self link, only valid for 'peering' zone types. | <code>string</code> | | <code>null</code> |
|
||||
| [recordsets](variables.tf#L85) | Map of DNS recordsets in \"type name\" => {ttl, [records]} format. | <code title="map(object({ ttl = optional(number, 300) records = list(string) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [service_directory_namespace](variables.tf#L102) | Service directory namespace id (URL), only valid for 'service-directory' zone types. | <code>string</code> | | <code>null</code> |
|
||||
| [type](variables.tf#L108) | Type of zone to create, valid values are 'public', 'private', 'forwarding', 'peering', 'service-directory'. | <code>string</code> | | <code>"private"</code> |
|
||||
| [zone_create](variables.tf#L118) | Create zone. When set to false, uses a data source to reference existing zone. | <code>bool</code> | | <code>true</code> |
|
||||
| [dnssec_config](variables.tf#L34) | DNSSEC configuration for this zone. | <code title="object({ non_existence = optional(string, "nsec3") state = string key_signing_key = optional(object( { algorithm = string, key_length = number }), { algorithm = "rsasha256", key_length = 2048 } ) zone_signing_key = optional(object( { algorithm = string, key_length = number }), { algorithm = "rsasha256", key_length = 1024 } ) })">object({…})</code> | | <code title="{ state = "off" }">{…}</code> |
|
||||
| [enable_logging](variables.tf#L65) | Enable query logging for this zone. Only valid for public zones. | <code>bool</code> | | <code>false</code> |
|
||||
| [forwarders](variables.tf#L59) | Map of {IPV4_ADDRESS => FORWARDING_PATH} for 'forwarding' zone types. Path can be 'default', 'private', or null for provider default. | <code>map(string)</code> | | <code>{}</code> |
|
||||
| [peer_network](variables.tf#L77) | Peering network self link, only valid for 'peering' zone types. | <code>string</code> | | <code>null</code> |
|
||||
| [recordsets](variables.tf#L88) | Map of DNS recordsets in \"type name\" => {ttl, [records]} format. | <code title="map(object({ ttl = optional(number, 300) records = optional(list(string)) geo_routing = optional(list(object({ location = string records = list(string) }))) wrr_routing = optional(list(object({ weight = number records = list(string) }))) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [service_directory_namespace](variables.tf#L123) | Service directory namespace id (URL), only valid for 'service-directory' zone types. | <code>string</code> | | <code>null</code> |
|
||||
| [type](variables.tf#L129) | Type of zone to create, valid values are 'public', 'private', 'forwarding', 'peering', 'service-directory'. | <code>string</code> | | <code>"private"</code> |
|
||||
| [zone_create](variables.tf#L139) | Create zone. When set to false, uses a data source to reference existing zone. | <code>bool</code> | | <code>true</code> |
|
||||
|
||||
## Outputs
|
||||
|
||||
|
|
|
@ -15,10 +15,42 @@
|
|||
*/
|
||||
|
||||
locals {
|
||||
recordsets = {
|
||||
# split record name and type and set as keys in a map
|
||||
_recordsets_0 = {
|
||||
for key, attrs in var.recordsets :
|
||||
key => merge(attrs, zipmap(["type", "name"], split(" ", key)))
|
||||
}
|
||||
# compute the final resource name for the recordset
|
||||
_recordsets = {
|
||||
for key, attrs in local._recordsets_0 :
|
||||
key => merge(attrs, {
|
||||
resource_name = (
|
||||
attrs.name == ""
|
||||
? var.domain
|
||||
: (
|
||||
substr(attrs.name, -1, 1) == "."
|
||||
? attrs.name
|
||||
: "${attrs.name}.${var.domain}"
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
# split recordsets between regular, geo and wrr
|
||||
geo_recordsets = {
|
||||
for k, v in local._recordsets :
|
||||
k => v
|
||||
if v.geo_routing != null
|
||||
}
|
||||
regular_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(
|
||||
|
@ -149,23 +181,72 @@ data "google_dns_keys" "dns_keys" {
|
|||
resource "google_dns_record_set" "cloud-static-records" {
|
||||
for_each = (
|
||||
var.type == "public" || var.type == "private"
|
||||
? local.recordsets
|
||||
? local.regular_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
|
||||
rrdatas = each.value.records
|
||||
name = each.value.resource_name
|
||||
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.resource_name
|
||||
type = each.value.type
|
||||
ttl = each.value.ttl
|
||||
|
||||
routing_policy {
|
||||
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.resource_name
|
||||
type = each.value.type
|
||||
ttl = each.value.ttl
|
||||
|
||||
routing_policy {
|
||||
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
|
||||
]
|
||||
|
|
|
@ -45,7 +45,10 @@ variable "dnssec_config" {
|
|||
{ algorithm = "rsasha256", key_length = 1024 }
|
||||
)
|
||||
})
|
||||
default = null
|
||||
default = {
|
||||
state = "off"
|
||||
}
|
||||
nullable = false
|
||||
}
|
||||
|
||||
variable "domain" {
|
||||
|
@ -86,17 +89,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" {
|
||||
|
|
|
@ -32,15 +32,27 @@ variable "peer_network" {
|
|||
}
|
||||
|
||||
variable "recordsets" {
|
||||
type = map(object({
|
||||
ttl = number
|
||||
records = list(string)
|
||||
}))
|
||||
type = any
|
||||
default = {
|
||||
"A localhost" = { ttl = 300, records = ["127.0.0.1"] }
|
||||
"A local-host.test.example." = { ttl = 300, records = ["127.0.0.2"] }
|
||||
"CNAME *" = { ttl = 300, records = ["localhost.example.org."] }
|
||||
"A " = { ttl = 300, records = ["127.0.0.3"] }
|
||||
"A geo" = {
|
||||
geo_routing = [
|
||||
{ location = "europe-west1", records = ["127.0.0.4"] },
|
||||
{ location = "europe-west2", records = ["127.0.0.5"] },
|
||||
{ location = "europe-west3", records = ["127.0.0.6"] }
|
||||
]
|
||||
}
|
||||
"A wrr" = {
|
||||
ttl = 600
|
||||
wrr_routing = [
|
||||
{ weight = 0.6, records = ["127.0.0.7"] },
|
||||
{ weight = 0.2, records = ["127.0.0.8"] },
|
||||
{ weight = 0.2, records = ["10.10.0.9"] }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,13 +12,13 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
def test_private(plan_runner):
|
||||
"Test private zone with three recordsets."
|
||||
_, resources = plan_runner()
|
||||
assert len(resources) == 5
|
||||
assert set(r['type'] for r in resources) == set([
|
||||
'google_dns_record_set', 'google_dns_managed_zone'
|
||||
])
|
||||
assert len(resources) == 7
|
||||
assert set(r['type'] for r in resources) == set(
|
||||
['google_dns_record_set', 'google_dns_managed_zone'])
|
||||
for r in resources:
|
||||
if r['type'] != 'google_dns_managed_zone':
|
||||
continue
|
||||
|
@ -29,15 +29,54 @@ def test_private(plan_runner):
|
|||
def test_private_recordsets(plan_runner):
|
||||
"Test recordsets in private zone."
|
||||
_, resources = plan_runner()
|
||||
recordsets = [r['values']
|
||||
for r in resources if r['type'] == 'google_dns_record_set']
|
||||
recordsets = [
|
||||
r['values'] for r in resources if r['type'] == 'google_dns_record_set'
|
||||
]
|
||||
|
||||
assert set(r['name'] for r in recordsets) == set([
|
||||
'localhost.test.example.',
|
||||
'local-host.test.example.',
|
||||
'*.test.example.',
|
||||
"test.example."
|
||||
'localhost.test.example.', 'local-host.test.example.', '*.test.example.',
|
||||
"test.example.", "geo.test.example.", "wrr.test.example."
|
||||
])
|
||||
|
||||
for r in recordsets:
|
||||
if r['name'] not in ['wrr.test.example.', 'geo.test.example.']:
|
||||
assert r['routing_policy'] == []
|
||||
assert r['rrdatas'] != []
|
||||
|
||||
geo_zone = [
|
||||
r['values'] for r in resources if r['address'] ==
|
||||
'module.test.google_dns_record_set.cloud-geo-records["A geo"]'
|
||||
][0]
|
||||
assert geo_zone['name'] == 'geo.test.example.'
|
||||
assert geo_zone['routing_policy'][0]['wrr'] == []
|
||||
assert geo_zone['routing_policy'][0]['geo'] == [{
|
||||
'location': 'europe-west1',
|
||||
'rrdatas': ['127.0.0.4']
|
||||
}, {
|
||||
'location': 'europe-west2',
|
||||
'rrdatas': ['127.0.0.5']
|
||||
}, {
|
||||
'location': 'europe-west3',
|
||||
'rrdatas': ['127.0.0.6']
|
||||
}]
|
||||
|
||||
wrr_zone = [
|
||||
r['values'] for r in resources if r['address'] ==
|
||||
'module.test.google_dns_record_set.cloud-wrr-records["A wrr"]'
|
||||
][0]
|
||||
assert wrr_zone['name'] == 'wrr.test.example.'
|
||||
assert wrr_zone['routing_policy'][0]['wrr'] == [{
|
||||
'rrdatas': ['127.0.0.7'],
|
||||
'weight': 0.6
|
||||
}, {
|
||||
'rrdatas': ['127.0.0.8'],
|
||||
'weight': 0.2
|
||||
}, {
|
||||
'rrdatas': ['10.10.0.9'],
|
||||
'weight': 0.2
|
||||
}]
|
||||
assert wrr_zone['routing_policy'][0]['geo'] == []
|
||||
|
||||
|
||||
def test_private_no_networks(plan_runner):
|
||||
"Test private zone not exposed to any network."
|
||||
|
@ -60,26 +99,31 @@ def test_forwarding_recordsets_null_forwarders(plan_runner):
|
|||
|
||||
def test_forwarding(plan_runner):
|
||||
"Test forwarding zone with single forwarder."
|
||||
_, resources = plan_runner(
|
||||
type='forwarding', recordsets='null',
|
||||
forwarders='{ "1.2.3.4" = null }')
|
||||
_, resources = plan_runner(type='forwarding', recordsets='null',
|
||||
forwarders='{ "1.2.3.4" = null }')
|
||||
assert len(resources) == 1
|
||||
resource = resources[0]
|
||||
assert resource['type'] == 'google_dns_managed_zone'
|
||||
assert resource['values']['forwarding_config'] == [{'target_name_servers': [
|
||||
{'forwarding_path': '', 'ipv4_address': '1.2.3.4'}]}]
|
||||
assert resource['values']['forwarding_config'] == [{
|
||||
'target_name_servers': [{
|
||||
'forwarding_path': '',
|
||||
'ipv4_address': '1.2.3.4'
|
||||
}]
|
||||
}]
|
||||
|
||||
|
||||
def test_peering(plan_runner):
|
||||
"Test peering zone."
|
||||
_, resources = plan_runner(type='peering',
|
||||
recordsets='null',
|
||||
_, resources = plan_runner(type='peering', recordsets='null',
|
||||
peer_network='dummy-vpc-self-link')
|
||||
assert len(resources) == 1
|
||||
resource = resources[0]
|
||||
assert resource['type'] == 'google_dns_managed_zone'
|
||||
assert resource['values']['peering_config'] == [
|
||||
{'target_network': [{'network_url': 'dummy-vpc-self-link'}]}]
|
||||
assert resource['values']['peering_config'] == [{
|
||||
'target_network': [{
|
||||
'network_url': 'dummy-vpc-self-link'
|
||||
}]
|
||||
}]
|
||||
|
||||
|
||||
def test_public(plan_runner):
|
||||
|
|
Loading…
Reference in New Issue