diff --git a/modules/dns/README.md b/modules/dns/README.md
index 6993e5695..207572c95 100644
--- a/modules/dns/README.md
+++ b/modules/dns/README.md
@@ -1,6 +1,8 @@
# Google Cloud DNS Module
-This module allows simple management of Google Cloud DNS zones and records. It supports creating public, private, forwarding, and peering zones. For DNSSEC configuration, refer to the [`dns_managed_zone` documentation](https://www.terraform.io/docs/providers/google/r/dns_managed_zone.html#dnssec_config).
+This module allows simple management of Google Cloud DNS zones and records. It supports creating public, private, forwarding, peering and service directory based zones.
+
+For DNSSEC configuration, refer to the [`dns_managed_zone` documentation](https://www.terraform.io/docs/providers/google/r/dns_managed_zone.html#dnssec_config).
## Example
@@ -32,9 +34,10 @@ module "private-dns" {
| *description* | Domain description. | string | | Terraform managed. |
| *dnssec_config* | DNSSEC configuration: kind, non_existence, state. | any | | {} |
| *forwarders* | List of target name servers, only valid for 'forwarding' zone types. | list(string) | | [] |
-| *peer_network* | Peering network self link, only valid for 'peering' zone types. | string | | |
+| *peer_network* | Peering network self link, only valid for 'peering' zone types. | string | | null |
| *recordsets* | List of DNS record objects to manage. | list(object({...})) | | [] |
-| *type* | Type of zone to create, valid values are 'public', 'private', 'forwarding', 'peering'. | string | | private |
+| *service_directory_namespace* | Service directory namespace id (URL), only valid for 'service-directory' zone types. | string | | null |
+| *type* | Type of zone to create, valid values are 'public', 'private', 'forwarding', 'peering', 'service-directory'. | string | | private |
## Outputs
diff --git a/modules/dns/main.tf b/modules/dns/main.tf
index 6e098b8d8..abb0beb2d 100644
--- a/modules/dns/main.tf
+++ b/modules/dns/main.tf
@@ -38,14 +38,11 @@ resource "google_dns_managed_zone" "non-public" {
dynamic forwarding_config {
for_each = (
- var.type == "forwarding" && var.forwarders != null
- ? { config = var.forwarders }
- : {}
+ var.type == "forwarding" && var.forwarders != null ? [""] : []
)
- iterator = config
content {
dynamic "target_name_servers" {
- for_each = config.value
+ for_each = var.forwarders
iterator = address
content {
ipv4_address = address.value
@@ -56,14 +53,11 @@ resource "google_dns_managed_zone" "non-public" {
dynamic peering_config {
for_each = (
- var.type == "peering" && var.peer_network != null
- ? { config = var.peer_network }
- : {}
+ var.type == "peering" && var.peer_network != null ? [""] : []
)
- iterator = config
content {
target_network {
- network_url = config.value
+ network_url = var.peer_network
}
}
}
@@ -78,6 +72,19 @@ resource "google_dns_managed_zone" "non-public" {
}
}
+ dynamic service_directory_config {
+ for_each = (
+ var.type == "service-directory" && var.service_directory_namespace != null
+ ? [""]
+ : []
+ )
+ content {
+ namespace {
+ namespace_url = var.service_directory_namespace
+ }
+ }
+ }
+
}
resource "google_dns_managed_zone" "public" {
diff --git a/modules/dns/variables.tf b/modules/dns/variables.tf
index 0991038c0..f38fb36a2 100644
--- a/modules/dns/variables.tf
+++ b/modules/dns/variables.tf
@@ -30,9 +30,6 @@ variable "description" {
default = "Terraform managed."
}
-# TODO(ludoo): add link to DNSSEC documentation in README
-# https://www.terraform.io/docs/providers/google/r/dns_managed_zone.html#dnssec_config
-
variable "default_key_specs_key" {
description = "DNSSEC default key signing specifications: algorithm, key_length, key_type, kind."
type = any
@@ -71,7 +68,7 @@ variable "name" {
variable "peer_network" {
description = "Peering network self link, only valid for 'peering' zone types."
type = string
- default = ""
+ default = null
}
variable "project_id" {
@@ -90,8 +87,14 @@ variable "recordsets" {
default = []
}
+variable "service_directory_namespace" {
+ description = "Service directory namespace id (URL), only valid for 'service-directory' zone types."
+ type = string
+ default = null
+}
+
variable "type" {
- description = "Type of zone to create, valid values are 'public', 'private', 'forwarding', 'peering'."
+ description = "Type of zone to create, valid values are 'public', 'private', 'forwarding', 'peering', 'service-directory'."
type = string
default = "private"
}
diff --git a/modules/service-directory/README.md b/modules/service-directory/README.md
new file mode 100644
index 000000000..c57bd9f8e
--- /dev/null
+++ b/modules/service-directory/README.md
@@ -0,0 +1,125 @@
+# Google Cloud Service Directory Module
+
+This module allows managing a single [Service Directory](https://cloud.google.com/service-directory) namespace, including multiple services, endpoints and IAM bindings at the namespace and service levels.
+
+It can be used in conjunction with the [DNS](../dns) module to create [service-directory based DNS zones](https://cloud.google.com/service-directory/docs/configuring-service-directory-zone, offloading IAM control of `A` and `SRV` records at the namespace or service level to Service Directory. The last examples shows how to wire the two modules together.
+
+
+## Examples
+
+### Namespace with IAM
+
+```hcl
+module "service-directory" {
+ source = "./modules/service-directory"
+ project_id = "my-project"
+ location = "europe-west1"
+ name = "sd-1"
+ iam_members = {
+ "roles/servicedirectory.editor" = [
+ "serviceAccount:namespace-editor@example.com"
+ ]
+ }
+ iam_roles = [
+ "roles/servicedirectory.editor"
+ ]
+}
+```
+
+### Services with IAM and endpoints
+
+```hcl
+module "service-directory" {
+ source = "./modules/service-directory"
+ project_id = "my-project"
+ location = "europe-west1"
+ name = "sd-1"
+ services = {
+ one = {
+ endpoints = ["first", "second"]
+ metadata = null
+ }
+ }
+ service_iam_members = {
+ one = {
+ "roles/servicedirectory.editor" = [
+ "serviceAccount:service-editor.example.com"
+ ]
+ }
+ }
+ service_iam_roles = {
+ one = ["roles/servicedirectory.editor"]
+ }
+ endpoint_config = {
+ "one/first" = { address = "127.0.0.1", port = 80, metadata = {} }
+ "one/second" = { address = "127.0.0.2", port = 80, metadata = {} }
+ }
+}
+```
+
+### DNS based zone
+
+Wiring a service directory namespace to a private DNS zone allows querying the namespace, and delegating control of DNS records at the namespace or service level. This effectively allows fine grained ACL control of Cloud DNS zones.
+
+```hcl
+module "service-directory" {
+ source = "./modules/service-directory"
+ project_id = "my-project"
+ location = "europe-west1"
+ name = "apps"
+ iam_members = {
+ "roles/servicedirectory.editor" = [
+ "serviceAccount:namespace-editor@example.com"
+ ]
+ }
+ iam_roles = [
+ "roles/servicedirectory.editor"
+ ]
+ services = {
+ app1 = { endpoints = ["one"], metadata = null }
+ }
+ endpoint_config = {
+ "app1/one" = { address = "127.0.0.1", port = 80, metadata = {} }
+ }
+}
+
+module "dns-sd" {
+ source = "./modules/dns"
+ project_id = "my-project"
+ type = "service-directory"
+ name = "apps"
+ domain = "apps.example.org."
+ client_networks = [local.vpc_self_link]
+ service_directory_namespace = module.service-directory.id
+}
+
+```
+
+
+## Variables
+
+| name | description | type | required | default |
+|---|---|:---: |:---:|:---:|
+| location | Namespace location. | string | ✓ | |
+| name | Namespace name. | string | ✓ | |
+| project_id | Project used for resources. | string | ✓ | |
+| *endpoint_config* | Map of endpoint attributes, keys are in service/endpoint format. | map(object({...})) | | {} |
+| *iam_members* | IAM members for each namespace role. | map(list(string)) | | {} |
+| *iam_roles* | IAM roles for the namespace. | list(string) | | [] |
+| *labels* | Labels. | map(string) | | {} |
+| *service_iam_members* | IAM members for each service and role. | map(map(list(string))) | | {} |
+| *service_iam_roles* | IAM roles for each service. | map(list(string)) | | {} |
+| *services* | Service configuration, using service names as keys. | map(object({...})) | | {} |
+
+## Outputs
+
+| name | description | sensitive |
+|---|---|:---:|
+| endpoints | Endpoint resources. | |
+| id | Namespace id (short name). | |
+| name | Namespace name (long name). | |
+| namespace | Namespace resource. | |
+| service_id | Service ids (short names). | |
+| service_names | Service ids (long names). | |
+| services | Service resources. | |
+
diff --git a/modules/service-directory/main.tf b/modules/service-directory/main.tf
new file mode 100644
index 000000000..8348de44a
--- /dev/null
+++ b/modules/service-directory/main.tf
@@ -0,0 +1,81 @@
+/**
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+locals {
+ endpoint_list = flatten([
+ for name, attrs in var.services : [
+ for endpoint in attrs.endpoints : { service : name, endpoint : endpoint }
+ ]
+ ])
+ endpoints = {
+ for ep in local.endpoint_list : "${ep.service}/${ep.endpoint}" => ep
+ }
+ iam_pairs = var.service_iam_roles == null ? [] : flatten([
+ for name, roles in var.service_iam_roles :
+ [for role in roles : { name = name, role = role }]
+ ])
+ iam_keypairs = {
+ for pair in local.iam_pairs :
+ "${pair.name}-${pair.role}" => pair
+ }
+ iam_members = (
+ var.service_iam_members == null ? {} : var.service_iam_members
+ )
+}
+
+resource "google_service_directory_namespace" "default" {
+ provider = google-beta
+ project = var.project_id
+ namespace_id = var.name
+ location = var.location
+ labels = var.labels
+}
+
+resource "google_service_directory_namespace_iam_binding" "default" {
+ provider = google-beta
+ for_each = toset(var.iam_roles)
+ name = google_service_directory_namespace.default.name
+ role = each.value
+ members = lookup(var.iam_members, each.value, [])
+}
+
+resource "google_service_directory_service" "default" {
+ provider = google-beta
+ for_each = var.services
+ namespace = google_service_directory_namespace.default.id
+ service_id = each.key
+ metadata = each.value.metadata
+}
+
+resource "google_service_directory_service_iam_binding" "default" {
+ provider = google-beta
+ for_each = local.iam_keypairs
+ name = google_service_directory_service.default[each.value.name].name
+ role = each.value.role
+ members = lookup(
+ lookup(local.iam_members, each.value.name, {}), each.value.role, []
+ )
+}
+
+resource "google_service_directory_endpoint" "default" {
+ provider = google-beta
+ for_each = local.endpoints
+ endpoint_id = each.value.endpoint
+ service = google_service_directory_service.default[each.value.service].id
+ metadata = try(var.endpoint_config[each.key].metadata, null)
+ address = try(var.endpoint_config[each.key].address, null)
+ port = try(var.endpoint_config[each.key].port, null)
+}
diff --git a/modules/service-directory/outputs.tf b/modules/service-directory/outputs.tf
new file mode 100644
index 000000000..0fab3996b
--- /dev/null
+++ b/modules/service-directory/outputs.tf
@@ -0,0 +1,66 @@
+/**
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+output "endpoints" {
+ description = "Endpoint resources."
+ value = google_service_directory_endpoint.default
+}
+
+output "id" {
+ description = "Namespace id (short name)."
+ value = google_service_directory_namespace.default.id
+}
+
+output "name" {
+ description = "Namespace name (long name)."
+ value = google_service_directory_namespace.default.name
+}
+
+output "namespace" {
+ description = "Namespace resource."
+ value = google_service_directory_namespace.default
+ depends_on = [
+ google_service_directory_namespace_iam_binding.default
+ ]
+}
+
+output "services" {
+ description = "Service resources."
+ value = google_service_directory_service.default
+ depends_on = [
+ google_service_directory_service_iam_binding.default
+ ]
+}
+
+output "service_id" {
+ description = "Service ids (short names)."
+ value = {
+ for k, v in google_service_directory_service.default : k => v.id
+ }
+ depends_on = [
+ google_service_directory_service_iam_binding.default
+ ]
+}
+
+output "service_names" {
+ description = "Service ids (long names)."
+ value = {
+ for k, v in google_service_directory_service.default : k => v.name
+ }
+ depends_on = [
+ google_service_directory_service_iam_binding.default
+ ]
+}
diff --git a/modules/service-directory/variables.tf b/modules/service-directory/variables.tf
new file mode 100644
index 000000000..8b921d56c
--- /dev/null
+++ b/modules/service-directory/variables.tf
@@ -0,0 +1,80 @@
+/**
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+# we need a separate variable as address will be dynamic in most cases
+variable "endpoint_config" {
+ description = "Map of endpoint attributes, keys are in service/endpoint format."
+ type = map(object({
+ address = string
+ port = number
+ metadata = map(string)
+ }))
+ default = {}
+}
+
+variable "iam_members" {
+ description = "IAM members for each namespace role."
+ type = map(list(string))
+ default = {}
+}
+
+variable "iam_roles" {
+ description = "IAM roles for the namespace."
+ type = list(string)
+ default = []
+}
+
+variable "labels" {
+ description = "Labels."
+ type = map(string)
+ default = {}
+}
+
+variable "location" {
+ description = "Namespace location."
+ type = string
+}
+
+variable "name" {
+ description = "Namespace name."
+ type = string
+}
+
+variable "project_id" {
+ description = "Project used for resources."
+ type = string
+}
+
+variable "service_iam_members" {
+ description = "IAM members for each service and role."
+ type = map(map(list(string)))
+ default = {}
+}
+
+variable "service_iam_roles" {
+ description = "IAM roles for each service."
+ type = map(list(string))
+ default = {}
+}
+
+variable "services" {
+ description = "Service configuration, using service names as keys."
+ type = map(object({
+ endpoints = list(string)
+ metadata = map(string)
+ }))
+ default = {}
+}
diff --git a/modules/service-directory/versions.tf b/modules/service-directory/versions.tf
new file mode 100644
index 000000000..ce6918e09
--- /dev/null
+++ b/modules/service-directory/versions.tf
@@ -0,0 +1,19 @@
+/**
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+terraform {
+ required_version = ">= 0.12.6"
+}