diff --git a/modules/folder/README.md b/modules/folder/README.md
index b85ba2f69..efb207e1d 100644
--- a/modules/folder/README.md
+++ b/modules/folder/README.md
@@ -140,7 +140,7 @@ module "folder-sink" {
name = "my-folder"
logging_sinks = {
warnings = {
- type = "gcs"
+ type = "storage"
destination = module.gcs.name
filter = "severity=WARNING"
include_children = true
@@ -215,26 +215,41 @@ module "folder2" {
}
# tftest modules=2 resources=6
```
+
+
+## Files
+
+| name | description | resources |
+|---|---|---|
+| [firewal_policies.tf](./firewal_policies.tf) | None | google_compute_firewall_policy · google_compute_firewall_policy_association · google_compute_firewall_policy_rule |
+| [iam.tf](./iam.tf) | IAM bindings, roles and audit logging resources. | google_folder_iam_binding |
+| [logging.tf](./logging.tf) | Log sinks and supporting resources. | google_bigquery_dataset_iam_member · google_logging_folder_exclusion · google_logging_folder_sink · google_project_iam_member · google_pubsub_topic_iam_member · google_storage_bucket_iam_member |
+| [main.tf](./main.tf) | Module-level locals and resources. | google_essential_contacts_contact · google_folder |
+| [organization_policies.tf](./organization_policies.tf) | Folder-level organization policies. | google_folder_organization_policy |
+| [outputs.tf](./outputs.tf) | Module outputs. | |
+| [variables.tf](./variables.tf) | Module variables. | |
+| [versions.tf](./versions.tf) | Version pins. | |
+
## Variables
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [contacts](variables.tf#L17) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES | map(list(string)) | | {} |
-| [firewall_policies](variables.tf#L23) | Hierarchical firewall policies created in this folder. | map(map(object({…}))) | | {} |
-| [firewall_policy_association](variables.tf#L39) | The hierarchical firewall policy to associate to this folder. Must be either a key in the `firewall_policies` map or the id of a policy defined somewhere else. | map(string) | | {} |
-| [firewall_policy_factory](variables.tf#L45) | Configuration for the firewall policy factory. | object({…}) | | null |
-| [folder_create](variables.tf#L55) | Create folder. When set to false, uses id to reference an existing folder. | bool | | true |
-| [group_iam](variables.tf#L61) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | map(list(string)) | | {} |
-| [iam](variables.tf#L67) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} |
-| [id](variables.tf#L73) | Folder ID in case you use folder_create=false | string | | null |
-| [logging_exclusions](variables.tf#L79) | Logging exclusions for this folder in the form {NAME -> FILTER}. | map(string) | | {} |
-| [logging_sinks](variables.tf#L85) | Logging sinks to create for this folder. | map(object({…})) | | {} |
-| [name](variables.tf#L98) | Folder name. | string | | null |
-| [parent](variables.tf#L104) | Parent in folders/folder_id or organizations/org_id format. | string | | null |
-| [policy_boolean](variables.tf#L114) | Map of boolean org policies and enforcement value, set value to null for policy restore. | map(bool) | | {} |
-| [policy_list](variables.tf#L120) | Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny. | map(object({…})) | | {} |
+| [firewall_policies](variables.tf#L24) | Hierarchical firewall policies created in this folder. | map(map(object({…}))) | | {} |
+| [firewall_policy_association](variables.tf#L41) | The hierarchical firewall policy to associate to this folder. Must be either a key in the `firewall_policies` map or the id of a policy defined somewhere else. | map(string) | | {} |
+| [firewall_policy_factory](variables.tf#L48) | Configuration for the firewall policy factory. | object({…}) | | null |
+| [folder_create](variables.tf#L58) | Create folder. When set to false, uses id to reference an existing folder. | bool | | true |
+| [group_iam](variables.tf#L64) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | map(list(string)) | | {} |
+| [iam](variables.tf#L71) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} |
+| [id](variables.tf#L78) | Folder ID in case you use folder_create=false | string | | null |
+| [logging_exclusions](variables.tf#L84) | Logging exclusions for this folder in the form {NAME -> FILTER}. | map(string) | | {} |
+| [logging_sinks](variables.tf#L91) | Logging sinks to create for this folder. | map(object({…})) | | {} |
+| [name](variables.tf#L112) | Folder name. | string | | null |
+| [parent](variables.tf#L118) | Parent in folders/folder_id or organizations/org_id format. | string | | null |
+| [policy_boolean](variables.tf#L128) | Map of boolean org policies and enforcement value, set value to null for policy restore. | map(bool) | | {} |
+| [policy_list](variables.tf#L135) | Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny. | map(object({…})) | | {} |
## Outputs
diff --git a/modules/folder/firewall-policy.tf b/modules/folder/firewal_policies.tf
similarity index 100%
rename from modules/folder/firewall-policy.tf
rename to modules/folder/firewal_policies.tf
diff --git a/modules/folder/iam.tf b/modules/folder/iam.tf
new file mode 100644
index 000000000..52886badb
--- /dev/null
+++ b/modules/folder/iam.tf
@@ -0,0 +1,40 @@
+/**
+ * Copyright 2022 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.
+ */
+
+# tfdoc:file:description IAM bindings, roles and audit logging resources.
+
+locals {
+ group_iam_roles = distinct(flatten(values(var.group_iam)))
+ group_iam = {
+ for r in local.group_iam_roles : r => [
+ for k, v in var.group_iam : "group:${k}" if try(index(v, r), null) != null
+ ]
+ }
+ iam = {
+ for role in distinct(concat(keys(var.iam), keys(local.group_iam))) :
+ role => concat(
+ try(var.iam[role], []),
+ try(local.group_iam[role], [])
+ )
+ }
+}
+
+resource "google_folder_iam_binding" "authoritative" {
+ for_each = local.iam
+ folder = local.folder.name
+ role = each.key
+ members = each.value
+}
diff --git a/modules/folder/logging.tf b/modules/folder/logging.tf
new file mode 100644
index 000000000..04df65103
--- /dev/null
+++ b/modules/folder/logging.tf
@@ -0,0 +1,91 @@
+/**
+ * Copyright 2022 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.
+ */
+
+# tfdoc:file:description Log sinks and supporting resources.
+
+locals {
+ sink_bindings = {
+ for type in ["bigquery", "pubsub", "logging", "storage"] :
+ type => {
+ for name, sink in var.logging_sinks :
+ name => sink
+ if sink.type == type
+ }
+ }
+}
+
+resource "google_logging_folder_sink" "sink" {
+ for_each = var.logging_sinks
+ name = each.key
+ #description = "${each.key} (Terraform-managed)"
+ folder = local.folder.name
+ destination = "${each.value.type}.googleapis.com/${each.value.destination}"
+ filter = each.value.filter
+ include_children = each.value.include_children
+
+ dynamic "exclusions" {
+ for_each = each.value.exclusions
+ iterator = exclusion
+ content {
+ name = exclusion.key
+ filter = exclusion.value
+ }
+ }
+
+ depends_on = [
+ google_folder_iam_binding.authoritative
+ ]
+}
+
+resource "google_storage_bucket_iam_member" "gcs-sinks-binding" {
+ for_each = local.sink_bindings["storage"]
+ bucket = each.value.destination
+ role = "roles/storage.objectCreator"
+ member = google_logging_folder_sink.sink[each.key].writer_identity
+}
+
+resource "google_bigquery_dataset_iam_member" "bq-sinks-binding" {
+ for_each = local.sink_bindings["bigquery"]
+ project = split("/", each.value.destination)[1]
+ dataset_id = split("/", each.value.destination)[3]
+ role = "roles/bigquery.dataEditor"
+ member = google_logging_folder_sink.sink[each.key].writer_identity
+}
+
+resource "google_pubsub_topic_iam_member" "pubsub-sinks-binding" {
+ for_each = local.sink_bindings["pubsub"]
+ project = split("/", each.value.destination)[1]
+ topic = split("/", each.value.destination)[3]
+ role = "roles/pubsub.publisher"
+ member = google_logging_folder_sink.sink[each.key].writer_identity
+}
+
+resource "google_project_iam_member" "bucket-sinks-binding" {
+ for_each = local.sink_bindings["logging"]
+ project = split("/", each.value.destination)[1]
+ role = "roles/logging.bucketWriter"
+ member = google_logging_folder_sink.sink[each.key].writer_identity
+ # TODO(jccb): use a condition to limit writer-identity only to this
+ # bucket
+}
+
+resource "google_logging_folder_exclusion" "logging-exclusion" {
+ for_each = var.logging_exclusions
+ name = each.key
+ folder = local.folder.name
+ description = "${each.key} (Terraform-managed)"
+ filter = each.value
+}
diff --git a/modules/folder/main.tf b/modules/folder/main.tf
index 028a87ea6..5d285d2d2 100644
--- a/modules/folder/main.tf
+++ b/modules/folder/main.tf
@@ -15,34 +15,6 @@
*/
locals {
- group_iam_roles = distinct(flatten(values(var.group_iam)))
- group_iam = {
- for r in local.group_iam_roles : r => [
- for k, v in var.group_iam : "group:${k}" if try(index(v, r), null) != null
- ]
- }
- iam = {
- for role in distinct(concat(keys(var.iam), keys(local.group_iam))) :
- role => concat(
- try(var.iam[role], []),
- try(local.group_iam[role], [])
- )
- }
- logging_sinks = coalesce(var.logging_sinks, {})
- sink_type_destination = {
- gcs = "storage.googleapis.com"
- bigquery = "bigquery.googleapis.com"
- pubsub = "pubsub.googleapis.com"
- logging = "logging.googleapis.com"
- }
- sink_bindings = {
- for type in ["gcs", "bigquery", "pubsub", "logging"] :
- type => {
- for name, sink in local.logging_sinks :
- name => sink
- if sink.type == type
- }
- }
folder = (
var.folder_create
? try(google_folder.folder.0, null)
@@ -61,149 +33,6 @@ resource "google_folder" "folder" {
parent = var.parent
}
-resource "google_folder_iam_binding" "authoritative" {
- for_each = local.iam
- folder = local.folder.name
- role = each.key
- members = each.value
-}
-
-resource "google_folder_organization_policy" "boolean" {
- for_each = var.policy_boolean
- folder = local.folder.name
- constraint = each.key
-
- dynamic "boolean_policy" {
- for_each = each.value == null ? [] : [each.value]
- iterator = policy
- content {
- enforced = policy.value
- }
- }
-
- dynamic "restore_policy" {
- for_each = each.value == null ? [""] : []
- content {
- default = true
- }
- }
-}
-
-resource "google_folder_organization_policy" "list" {
- for_each = var.policy_list
- folder = local.folder.name
- constraint = each.key
-
- dynamic "list_policy" {
- for_each = each.value.status == null ? [] : [each.value]
- iterator = policy
- content {
- inherit_from_parent = policy.value.inherit_from_parent
- suggested_value = policy.value.suggested_value
- dynamic "allow" {
- for_each = policy.value.status ? [""] : []
- content {
- values = (
- try(length(policy.value.values) > 0, false)
- ? policy.value.values
- : null
- )
- all = (
- try(length(policy.value.values) > 0, false)
- ? null
- : true
- )
- }
- }
- dynamic "deny" {
- for_each = policy.value.status ? [] : [""]
- content {
- values = (
- try(length(policy.value.values) > 0, false)
- ? policy.value.values
- : null
- )
- all = (
- try(length(policy.value.values) > 0, false)
- ? null
- : true
- )
- }
- }
- }
- }
-
- dynamic "restore_policy" {
- for_each = each.value.status == null ? [true] : []
- content {
- default = true
- }
- }
-}
-
-resource "google_logging_folder_sink" "sink" {
- for_each = local.logging_sinks
- name = each.key
- #description = "${each.key} (Terraform-managed)"
- folder = local.folder.name
- destination = "${local.sink_type_destination[each.value.type]}/${each.value.destination}"
- filter = each.value.filter
- include_children = each.value.include_children
-
- dynamic "exclusions" {
- for_each = each.value.exclusions
- iterator = exclusion
- content {
- name = exclusion.key
- filter = exclusion.value
- }
- }
-
- depends_on = [
- google_folder_iam_binding.authoritative
- ]
-}
-
-resource "google_storage_bucket_iam_member" "gcs-sinks-binding" {
- for_each = local.sink_bindings["gcs"]
- bucket = each.value.destination
- role = "roles/storage.objectCreator"
- member = google_logging_folder_sink.sink[each.key].writer_identity
-}
-
-resource "google_bigquery_dataset_iam_member" "bq-sinks-binding" {
- for_each = local.sink_bindings["bigquery"]
- project = split("/", each.value.destination)[1]
- dataset_id = split("/", each.value.destination)[3]
- role = "roles/bigquery.dataEditor"
- member = google_logging_folder_sink.sink[each.key].writer_identity
-}
-
-resource "google_pubsub_topic_iam_member" "pubsub-sinks-binding" {
- for_each = local.sink_bindings["pubsub"]
- project = split("/", each.value.destination)[1]
- topic = split("/", each.value.destination)[3]
- role = "roles/pubsub.publisher"
- member = google_logging_folder_sink.sink[each.key].writer_identity
-}
-
-resource "google_project_iam_member" "bucket-sinks-binding" {
- for_each = local.sink_bindings["logging"]
- project = split("/", each.value.destination)[1]
- role = "roles/logging.bucketWriter"
- member = google_logging_folder_sink.sink[each.key].writer_identity
- # TODO(jccb): use a condition to limit writer-identity only to this
- # bucket
-}
-
-resource "google_logging_folder_exclusion" "logging-exclusion" {
- for_each = coalesce(var.logging_exclusions, {})
- name = each.key
- folder = local.folder.name
- description = "${each.key} (Terraform-managed)"
- filter = each.value
-}
-
resource "google_essential_contacts_contact" "contact" {
provider = google-beta
for_each = var.contacts
diff --git a/modules/folder/organization_policies.tf b/modules/folder/organization_policies.tf
new file mode 100644
index 000000000..177a3d804
--- /dev/null
+++ b/modules/folder/organization_policies.tf
@@ -0,0 +1,90 @@
+/**
+ * Copyright 2022 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.
+ */
+
+# tfdoc:file:description Folder-level organization policies.
+
+resource "google_folder_organization_policy" "boolean" {
+ for_each = var.policy_boolean
+ folder = local.folder.name
+ constraint = each.key
+
+ dynamic "boolean_policy" {
+ for_each = each.value == null ? [] : [each.value]
+ iterator = policy
+ content {
+ enforced = policy.value
+ }
+ }
+
+ dynamic "restore_policy" {
+ for_each = each.value == null ? [""] : []
+ content {
+ default = true
+ }
+ }
+}
+
+resource "google_folder_organization_policy" "list" {
+ for_each = var.policy_list
+ folder = local.folder.name
+ constraint = each.key
+
+ dynamic "list_policy" {
+ for_each = each.value.status == null ? [] : [each.value]
+ iterator = policy
+ content {
+ inherit_from_parent = policy.value.inherit_from_parent
+ suggested_value = policy.value.suggested_value
+ dynamic "allow" {
+ for_each = policy.value.status ? [""] : []
+ content {
+ values = (
+ try(length(policy.value.values) > 0, false)
+ ? policy.value.values
+ : null
+ )
+ all = (
+ try(length(policy.value.values) > 0, false)
+ ? null
+ : true
+ )
+ }
+ }
+ dynamic "deny" {
+ for_each = policy.value.status ? [] : [""]
+ content {
+ values = (
+ try(length(policy.value.values) > 0, false)
+ ? policy.value.values
+ : null
+ )
+ all = (
+ try(length(policy.value.values) > 0, false)
+ ? null
+ : true
+ )
+ }
+ }
+ }
+ }
+
+ dynamic "restore_policy" {
+ for_each = each.value.status == null ? [true] : []
+ content {
+ default = true
+ }
+ }
+}
diff --git a/modules/folder/variables.tf b/modules/folder/variables.tf
index 9fc22b52b..563831cb6 100644
--- a/modules/folder/variables.tf
+++ b/modules/folder/variables.tf
@@ -18,6 +18,7 @@ variable "contacts" {
description = "List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES"
type = map(list(string))
default = {}
+ nullable = false
}
variable "firewall_policies" {
@@ -33,13 +34,15 @@ variable "firewall_policies" {
target_resources = list(string)
target_service_accounts = list(string)
})))
- default = {}
+ default = {}
+ nullable = false
}
variable "firewall_policy_association" {
description = "The hierarchical firewall policy to associate to this folder. Must be either a key in the `firewall_policies` map or the id of a policy defined somewhere else."
type = map(string)
default = {}
+ nullable = false
}
variable "firewall_policy_factory" {
@@ -62,12 +65,14 @@ variable "group_iam" {
description = "Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable."
type = map(list(string))
default = {}
+ nullable = false
}
variable "iam" {
description = "IAM bindings in {ROLE => [MEMBERS]} format."
type = map(list(string))
default = {}
+ nullable = false
}
variable "id" {
@@ -80,6 +85,7 @@ variable "logging_exclusions" {
description = "Logging exclusions for this folder in the form {NAME -> FILTER}."
type = map(string)
default = {}
+ nullable = false
}
variable "logging_sinks" {
@@ -92,7 +98,15 @@ variable "logging_sinks" {
# TODO exclusions also support description and disabled
exclusions = map(string)
}))
- default = {}
+ validation {
+ condition = alltrue([
+ for k, v in(var.logging_sinks == null ? {} : var.logging_sinks) :
+ contains(["bigquery", "logging", "pubsub", "storage"], v.type)
+ ])
+ error_message = "Type must be one of 'bigquery', 'logging', 'pubsub', 'storage'."
+ }
+ default = {}
+ nullable = false
}
variable "name" {
@@ -115,6 +129,7 @@ variable "policy_boolean" {
description = "Map of boolean org policies and enforcement value, set value to null for policy restore."
type = map(bool)
default = {}
+ nullable = false
}
variable "policy_list" {
@@ -125,5 +140,6 @@ variable "policy_list" {
status = bool
values = list(string)
}))
- default = {}
+ default = {}
+ nullable = false
}
diff --git a/modules/folder/versions.tf b/modules/folder/versions.tf
index 290412687..e72a78007 100644
--- a/modules/folder/versions.tf
+++ b/modules/folder/versions.tf
@@ -13,7 +13,7 @@
# limitations under the License.
terraform {
- required_version = ">= 1.0.0"
+ required_version = ">= 1.1.0"
required_providers {
google = {
source = "hashicorp/google"
diff --git a/modules/organization/README.md b/modules/organization/README.md
index 4cda102be..8edc529d7 100644
--- a/modules/organization/README.md
+++ b/modules/organization/README.md
@@ -243,11 +243,11 @@ module "org" {
| name | description | resources |
|---|---|---|
-| [firewall-policy.tf](./firewall-policy.tf) | Hierarchical firewall policies. | google_compute_firewall_policy · google_compute_firewall_policy_association · google_compute_firewall_policy_rule |
+| [firewall_policies.tf](./firewall_policies.tf) | Hierarchical firewall policies. | google_compute_firewall_policy · google_compute_firewall_policy_association · google_compute_firewall_policy_rule |
| [iam.tf](./iam.tf) | IAM bindings, roles and audit logging resources. | google_organization_iam_audit_config · google_organization_iam_binding · google_organization_iam_custom_role · google_organization_iam_member · google_organization_iam_policy |
| [logging.tf](./logging.tf) | Log sinks and supporting resources. | google_bigquery_dataset_iam_member · google_logging_organization_exclusion · google_logging_organization_sink · google_project_iam_member · google_pubsub_topic_iam_member · google_storage_bucket_iam_member |
| [main.tf](./main.tf) | Module-level locals and resources. | google_essential_contacts_contact |
-| [org-policy.tf](./org-policy.tf) | Organization policies. | google_organization_policy |
+| [organization_policies.tf](./organization_policies.tf) | Organization-level organization policies. | google_organization_policy |
| [outputs.tf](./outputs.tf) | Module outputs. | |
| [variables.tf](./variables.tf) | Module variables. | |
| [versions.tf](./versions.tf) | Version pins. | |
diff --git a/modules/organization/firewall-policy.tf b/modules/organization/firewall_policies.tf
similarity index 100%
rename from modules/organization/firewall-policy.tf
rename to modules/organization/firewall_policies.tf
diff --git a/modules/organization/org-policy.tf b/modules/organization/organization_policies.tf
similarity index 97%
rename from modules/organization/org-policy.tf
rename to modules/organization/organization_policies.tf
index c00783791..f23a98b48 100644
--- a/modules/organization/org-policy.tf
+++ b/modules/organization/organization_policies.tf
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-# tfdoc:file:description Organization policies.
+# tfdoc:file:description Organization-level organization policies.
resource "google_organization_policy" "boolean" {
for_each = var.policy_boolean
diff --git a/modules/project/README.md b/modules/project/README.md
index b021fdcb7..170160325 100644
--- a/modules/project/README.md
+++ b/modules/project/README.md
@@ -108,7 +108,7 @@ module "project-host" {
parent = "folders/1234567890"
logging_sinks = {
warnings = {
- type = "gcs"
+ type = "storage"
destination = module.gcs.name
filter = "severity=WARNING"
iam = false
@@ -184,49 +184,49 @@ module "project" {
| [iam.tf](./iam.tf) | Generic and OSLogin-specific IAM bindings and roles. | google_project_iam_binding · google_project_iam_custom_role · google_project_iam_member |
| [logging.tf](./logging.tf) | Log sinks and supporting resources. | google_bigquery_dataset_iam_member · google_logging_project_exclusion · google_logging_project_sink · google_project_iam_member · google_pubsub_topic_iam_member · google_storage_bucket_iam_member |
| [main.tf](./main.tf) | Module-level locals and resources. | google_compute_project_metadata_item · google_essential_contacts_contact · google_monitoring_monitored_project · google_project · google_project_service · google_resource_manager_lien |
-| [organization-policies.tf](./organization-policies.tf) | Project-level organization policies. | google_project_organization_policy |
+| [organization_policies.tf](./organization_policies.tf) | Project-level organization policies. | google_project_organization_policy |
| [outputs.tf](./outputs.tf) | Module outputs. | |
| [service_accounts.tf](./service_accounts.tf) | Service identities and supporting resources. | google_kms_crypto_key_iam_member · google_project_service_identity |
| [shared_vpc.tf](./shared_vpc.tf) | Shared VPC project-level configuration. | google_compute_shared_vpc_host_project · google_compute_shared_vpc_service_project |
| [variables.tf](./variables.tf) | Module variables. | |
| [versions.tf](./versions.tf) | Version pins. | |
-| [vpc-sc.tf](./vpc-sc.tf) | VPC-SC project-level perimeter configuration. | google_access_context_manager_service_perimeter_resource |
+| [vpc_sc.tf](./vpc_sc.tf) | VPC-SC project-level perimeter configuration. | google_access_context_manager_service_perimeter_resource |
## Variables
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [name](variables.tf#L109) | Project name and id suffix. | string | ✓ | |
+| [name](variables.tf#L125) | Project name and id suffix. | string | ✓ | |
| [auto_create_network](variables.tf#L17) | Whether to create the default network for the project | bool | | false |
| [billing_account](variables.tf#L23) | Billing account id. | string | | null |
| [contacts](variables.tf#L29) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES | map(list(string)) | | {} |
-| [custom_roles](variables.tf#L35) | Map of role name => list of permissions to create in this project. | map(list(string)) | | {} |
-| [descriptive_name](variables.tf#L41) | Name of the project name. Used for project name instead of `name` variable | string | | null |
-| [group_iam](variables.tf#L47) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | map(list(string)) | | {} |
-| [iam](variables.tf#L53) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} |
-| [iam_additive](variables.tf#L59) | IAM additive bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} |
-| [iam_additive_members](variables.tf#L65) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | map(list(string)) | | {} |
-| [labels](variables.tf#L71) | Resource labels. | map(string) | | {} |
-| [lien_reason](variables.tf#L77) | If non-empty, creates a project lien with this description. | string | | "" |
-| [logging_exclusions](variables.tf#L83) | Logging exclusions for this project in the form {NAME -> FILTER}. | map(string) | | {} |
-| [logging_sinks](variables.tf#L89) | Logging sinks to create for this project. | map(object({…})) | | {} |
-| [metric_scopes](variables.tf#L103) | List of projects that will act as metric scopes for this project. | list(string) | | null |
-| [oslogin](variables.tf#L114) | Enable OS Login. | bool | | false |
-| [oslogin_admins](variables.tf#L120) | List of IAM-style identities that will be granted roles necessary for OS Login administrators. | list(string) | | [] |
-| [oslogin_users](variables.tf#L126) | List of IAM-style identities that will be granted roles necessary for OS Login users. | list(string) | | [] |
-| [parent](variables.tf#L132) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | string | | null |
-| [policy_boolean](variables.tf#L142) | Map of boolean org policies and enforcement value, set value to null for policy restore. | map(bool) | | {} |
-| [policy_list](variables.tf#L148) | Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny. | map(object({…})) | | {} |
-| [prefix](variables.tf#L159) | Prefix used to generate project id and name. | string | | null |
-| [project_create](variables.tf#L165) | Create project. When set to false, uses a data source to reference existing project. | bool | | true |
-| [service_config](variables.tf#L171) | Configure service API activation. | object({…}) | | {…} |
-| [service_encryption_key_ids](variables.tf#L183) | Cloud KMS encryption key in {SERVICE => [KEY_URL]} format. | map(list(string)) | | {} |
-| [service_perimeter_bridges](variables.tf#L190) | Name of VPC-SC Bridge perimeters to add project into. See comment in the variables file for format. | list(string) | | null |
-| [service_perimeter_standard](variables.tf#L197) | Name of VPC-SC Standard perimeter to add project into. See comment in the variables file for format. | string | | null |
-| [services](variables.tf#L203) | Service APIs to enable. | list(string) | | [] |
-| [shared_vpc_host_config](variables.tf#L209) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | object({…}) | | {…} |
-| [shared_vpc_service_config](variables.tf#L221) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | object({…}) | | {…} |
-| [skip_delete](variables.tf#L233) | Allows the underlying resources to be destroyed without destroying the project itself. | bool | | false |
+| [custom_roles](variables.tf#L36) | Map of role name => list of permissions to create in this project. | map(list(string)) | | {} |
+| [descriptive_name](variables.tf#L43) | Name of the project name. Used for project name instead of `name` variable | string | | null |
+| [group_iam](variables.tf#L49) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | map(list(string)) | | {} |
+| [iam](variables.tf#L56) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} |
+| [iam_additive](variables.tf#L63) | IAM additive bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} |
+| [iam_additive_members](variables.tf#L70) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | map(list(string)) | | {} |
+| [labels](variables.tf#L76) | Resource labels. | map(string) | | {} |
+| [lien_reason](variables.tf#L83) | If non-empty, creates a project lien with this description. | string | | "" |
+| [logging_exclusions](variables.tf#L89) | Logging exclusions for this project in the form {NAME -> FILTER}. | map(string) | | {} |
+| [logging_sinks](variables.tf#L96) | Logging sinks to create for this project. | map(object({…})) | | {} |
+| [metric_scopes](variables.tf#L118) | List of projects that will act as metric scopes for this project. | list(string) | | [] |
+| [oslogin](variables.tf#L130) | Enable OS Login. | bool | | false |
+| [oslogin_admins](variables.tf#L136) | List of IAM-style identities that will be granted roles necessary for OS Login administrators. | list(string) | | [] |
+| [oslogin_users](variables.tf#L144) | List of IAM-style identities that will be granted roles necessary for OS Login users. | list(string) | | [] |
+| [parent](variables.tf#L151) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | string | | null |
+| [policy_boolean](variables.tf#L161) | Map of boolean org policies and enforcement value, set value to null for policy restore. | map(bool) | | {} |
+| [policy_list](variables.tf#L168) | Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny. | map(object({…})) | | {} |
+| [prefix](variables.tf#L180) | Prefix used to generate project id and name. | string | | null |
+| [project_create](variables.tf#L186) | Create project. When set to false, uses a data source to reference existing project. | bool | | true |
+| [service_config](variables.tf#L192) | Configure service API activation. | object({…}) | | {…} |
+| [service_encryption_key_ids](variables.tf#L204) | Cloud KMS encryption key in {SERVICE => [KEY_URL]} format. | map(list(string)) | | {} |
+| [service_perimeter_bridges](variables.tf#L211) | Name of VPC-SC Bridge perimeters to add project into. See comment in the variables file for format. | list(string) | | null |
+| [service_perimeter_standard](variables.tf#L218) | Name of VPC-SC Standard perimeter to add project into. See comment in the variables file for format. | string | | null |
+| [services](variables.tf#L224) | Service APIs to enable. | list(string) | | [] |
+| [shared_vpc_host_config](variables.tf#L230) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | object({…}) | | {…} |
+| [shared_vpc_service_config](variables.tf#L243) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | object({…}) | | {…} |
+| [skip_delete](variables.tf#L256) | Allows the underlying resources to be destroyed without destroying the project itself. | bool | | false |
## Outputs
diff --git a/modules/project/logging.tf b/modules/project/logging.tf
index 5a22a63ec..53bc2f030 100644
--- a/modules/project/logging.tf
+++ b/modules/project/logging.tf
@@ -17,28 +17,21 @@
# tfdoc:file:description Log sinks and supporting resources.
locals {
- logging_sinks = coalesce(var.logging_sinks, {})
sink_bindings = {
- for type in ["gcs", "bigquery", "pubsub", "logging"] :
+ for type in ["bigquery", "pubsub", "logging", "storage"] :
type => {
- for name, sink in local.logging_sinks :
+ for name, sink in var.logging_sinks :
name => sink if sink.iam && sink.type == type
}
}
- sink_type_destination = {
- gcs = "storage.googleapis.com"
- bigquery = "bigquery.googleapis.com"
- pubsub = "pubsub.googleapis.com"
- logging = "logging.googleapis.com"
- }
}
resource "google_logging_project_sink" "sink" {
- for_each = local.logging_sinks
+ for_each = var.logging_sinks
name = each.key
#description = "${each.key} (Terraform-managed)"
project = local.project.project_id
- destination = "${local.sink_type_destination[each.value.type]}/${each.value.destination}"
+ destination = "${each.value.type}.googleapis.com/${each.value.destination}"
filter = each.value.filter
unique_writer_identity = each.value.unique_writer
@@ -58,7 +51,7 @@ resource "google_logging_project_sink" "sink" {
}
resource "google_storage_bucket_iam_member" "gcs-sinks-binding" {
- for_each = local.sink_bindings["gcs"]
+ for_each = local.sink_bindings["storage"]
bucket = each.value.destination
role = "roles/storage.objectCreator"
member = google_logging_project_sink.sink[each.key].writer_identity
@@ -90,7 +83,7 @@ resource "google_project_iam_member" "bucket-sinks-binding" {
}
resource "google_logging_project_exclusion" "logging-exclusion" {
- for_each = coalesce(var.logging_exclusions, {})
+ for_each = var.logging_exclusions
name = each.key
project = local.project.project_id
description = "${each.key} (Terraform-managed)"
diff --git a/modules/project/main.tf b/modules/project/main.tf
index 89af9501f..9f0dff497 100644
--- a/modules/project/main.tf
+++ b/modules/project/main.tf
@@ -89,7 +89,7 @@ resource "google_essential_contacts_contact" "contact" {
resource "google_monitoring_monitored_project" "primary" {
provider = google-beta
- for_each = toset(coalesce(var.metric_scopes, []))
+ for_each = toset(var.metric_scopes)
metrics_scope = each.value
name = local.project.project_id
}
diff --git a/modules/project/organization-policies.tf b/modules/project/organization_policies.tf
similarity index 100%
rename from modules/project/organization-policies.tf
rename to modules/project/organization_policies.tf
diff --git a/modules/project/variables.tf b/modules/project/variables.tf
index 4b581fa53..00f20f949 100644
--- a/modules/project/variables.tf
+++ b/modules/project/variables.tf
@@ -30,12 +30,14 @@ variable "contacts" {
description = "List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES"
type = map(list(string))
default = {}
+ nullable = false
}
variable "custom_roles" {
description = "Map of role name => list of permissions to create in this project."
type = map(list(string))
default = {}
+ nullable = false
}
variable "descriptive_name" {
@@ -48,18 +50,21 @@ variable "group_iam" {
description = "Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable."
type = map(list(string))
default = {}
+ nullable = false
}
variable "iam" {
description = "IAM bindings in {ROLE => [MEMBERS]} format."
type = map(list(string))
default = {}
+ nullable = false
}
variable "iam_additive" {
description = "IAM additive bindings in {ROLE => [MEMBERS]} format."
type = map(list(string))
default = {}
+ nullable = false
}
variable "iam_additive_members" {
@@ -72,6 +77,7 @@ variable "labels" {
description = "Resource labels."
type = map(string)
default = {}
+ nullable = false
}
variable "lien_reason" {
@@ -84,6 +90,7 @@ variable "logging_exclusions" {
description = "Logging exclusions for this project in the form {NAME -> FILTER}."
type = map(string)
default = {}
+ nullable = false
}
variable "logging_sinks" {
@@ -97,13 +104,22 @@ variable "logging_sinks" {
# TODO exclusions also support description and disabled
exclusions = map(string)
}))
- default = {}
+ validation {
+ condition = alltrue([
+ for k, v in(var.logging_sinks == null ? {} : var.logging_sinks) :
+ contains(["bigquery", "logging", "pubsub", "storage"], v.type)
+ ])
+ error_message = "Type must be one of 'bigquery', 'logging', 'pubsub', 'storage'."
+ }
+ default = {}
+ nullable = false
}
variable "metric_scopes" {
description = "List of projects that will act as metric scopes for this project."
type = list(string)
- default = null
+ default = []
+ nullable = false
}
variable "name" {
@@ -121,12 +137,15 @@ variable "oslogin_admins" {
description = "List of IAM-style identities that will be granted roles necessary for OS Login administrators."
type = list(string)
default = []
+ nullable = false
+
}
variable "oslogin_users" {
description = "List of IAM-style identities that will be granted roles necessary for OS Login users."
type = list(string)
default = []
+ nullable = false
}
variable "parent" {
@@ -143,6 +162,7 @@ variable "policy_boolean" {
description = "Map of boolean org policies and enforcement value, set value to null for policy restore."
type = map(bool)
default = {}
+ nullable = false
}
variable "policy_list" {
@@ -153,7 +173,8 @@ variable "policy_list" {
status = bool
values = list(string)
}))
- default = {}
+ default = {}
+ nullable = false
}
variable "prefix" {
@@ -216,6 +237,7 @@ variable "shared_vpc_host_config" {
enabled = false
service_projects = []
}
+ nullable = false
}
variable "shared_vpc_service_config" {
@@ -228,6 +250,7 @@ variable "shared_vpc_service_config" {
attach = false
host_project = ""
}
+ nullable = false
}
variable "skip_delete" {
diff --git a/modules/project/versions.tf b/modules/project/versions.tf
index 290412687..e72a78007 100644
--- a/modules/project/versions.tf
+++ b/modules/project/versions.tf
@@ -13,7 +13,7 @@
# limitations under the License.
terraform {
- required_version = ">= 1.0.0"
+ required_version = ">= 1.1.0"
required_providers {
google = {
source = "hashicorp/google"
diff --git a/modules/project/vpc-sc.tf b/modules/project/vpc_sc.tf
similarity index 100%
rename from modules/project/vpc-sc.tf
rename to modules/project/vpc_sc.tf
diff --git a/tests/modules/folder/test_plan_logging.py b/tests/modules/folder/test_plan_logging.py
index 9e323f57a..9f3da533d 100644
--- a/tests/modules/folder/test_plan_logging.py
+++ b/tests/modules/folder/test_plan_logging.py
@@ -18,7 +18,7 @@ def test_sinks(plan_runner):
"Test folder-level sinks."
logging_sinks = """ {
warning = {
- type = "gcs"
+ type = "storage"
destination = "mybucket"
filter = "severity=WARNING"
iam = true
diff --git a/tests/modules/project/test_plan_logging.py b/tests/modules/project/test_plan_logging.py
index 4c0a4c325..2b847f99a 100644
--- a/tests/modules/project/test_plan_logging.py
+++ b/tests/modules/project/test_plan_logging.py
@@ -19,7 +19,7 @@ def test_sinks(plan_runner):
"Test folder-level sinks."
logging_sinks = """ {
warning = {
- type = "gcs"
+ type = "storage"
destination = "mybucket"
filter = "severity=WARNING"
iam = true