Update logging sink to tf1.3 in resman modules

This commit is contained in:
Julio Castillo 2022-11-11 19:05:39 +01:00
parent e6947dd6a6
commit 486d398c7d
21 changed files with 484 additions and 418 deletions

View File

@ -192,13 +192,9 @@ module "organization" {
}
logging_sinks = {
for name, attrs in var.log_sinks : name => {
bq_partitioned_table = attrs.type == "bigquery"
destination = local.log_sink_destinations[name].id
exclusions = {}
filter = attrs.filter
iam = true
include_children = true
type = attrs.type
destination = local.log_sink_destinations[name].as_logging_destination
filter = attrs.filter
bigquery_use_partitioned_table = attrs.type == "bigquery"
}
}
}

View File

@ -14,6 +14,21 @@
* limitations under the License.
*/
output "as_logging_destination" {
description = "Parameters to use this dataset as a log sink destination."
value = {
type = "bigquery"
target = google_bigquery_dataset.default.id
}
depends_on = [
google_bigquery_dataset_access.domain,
google_bigquery_dataset_access.group_by_email,
google_bigquery_dataset_access.special_group,
google_bigquery_dataset_access.user_by_email,
google_bigquery_dataset_access.views
]
}
output "dataset" {
description = "Dataset resource."
value = google_bigquery_dataset.default

View File

@ -166,37 +166,27 @@ module "bucket" {
id = "bucket"
}
module "folder-sink" {
source = "./fabric/modules/folder"
parent = "folders/657104291943"
name = "my-folder"
logging_sinks = {
warnings = {
type = "storage"
destination = module.gcs.id
filter = "severity=WARNING"
include_children = true
exclusions = {}
destination = module.gcs.as_logging_destination
filter = "severity=WARNING"
}
info = {
type = "bigquery"
destination = module.dataset.id
filter = "severity=INFO"
include_children = true
exclusions = {}
destination = module.dataset.as_logging_destination
filter = "severity=INFO"
}
notice = {
type = "pubsub"
destination = module.pubsub.id
filter = "severity=NOTICE"
include_children = true
exclusions = {}
destination = module.pubsub.as_logging_destination
filter = "severity=NOTICE"
}
debug = {
type = "logging"
destination = module.bucket.id
filter = "severity=DEBUG"
include_children = true
destination = module.bucket.as_logging_destination
filter = "severity=DEBUG"
exclusions = {
no-compute = "logName:compute"
}
@ -312,12 +302,12 @@ module "folder" {
| [iam_additive_members](variables.tf#L85) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [id](variables.tf#L92) | Folder ID in case you use folder_create=false. | <code>string</code> | | <code>null</code> |
| [logging_exclusions](variables.tf#L98) | Logging exclusions for this folder in the form {NAME -> FILTER}. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [logging_sinks](variables.tf#L105) | Logging sinks to create for this folder. | <code title="map&#40;object&#40;&#123;&#10; destination &#61; string&#10; type &#61; string&#10; filter &#61; string&#10; include_children &#61; bool&#10; exclusions &#61; map&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [name](variables.tf#L126) | Folder name. | <code>string</code> | | <code>null</code> |
| [org_policies](variables.tf#L132) | Organization policies applied to this folder keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; condition &#61; object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [org_policies_data_path](variables.tf#L172) | Path containing org policies in YAML format. | <code>string</code> | | <code>null</code> |
| [parent](variables.tf#L178) | Parent in folders/folder_id or organizations/org_id format. | <code>string</code> | | <code>null</code> |
| [tag_bindings](variables.tf#L188) | Tag bindings for this folder, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> |
| [logging_sinks](variables.tf#L105) | Logging sinks to create for this folder. | <code title="map&#40;object&#40;&#123;&#10; bigquery_use_partitioned_table &#61; optional&#40;bool&#41;&#10; description &#61; optional&#40;string&#41;&#10; destination &#61; object&#40;&#123;&#10; type &#61; string&#10; target &#61; string&#10; &#125;&#41;&#10; disabled &#61; optional&#40;bool, false&#41;&#10; exclusions &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; filter &#61; string&#10; include_children &#61; optional&#40;bool, true&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [name](variables.tf#L137) | Folder name. | <code>string</code> | | <code>null</code> |
| [org_policies](variables.tf#L143) | Organization policies applied to this folder keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; condition &#61; object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [org_policies_data_path](variables.tf#L183) | Path containing org policies in YAML format. | <code>string</code> | | <code>null</code> |
| [parent](variables.tf#L189) | Parent in folders/folder_id or organizations/org_id format. | <code>string</code> | | <code>null</code> |
| [tag_bindings](variables.tf#L199) | Tag bindings for this folder, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> |
## Outputs

View File

@ -22,19 +22,27 @@ locals {
type => {
for name, sink in var.logging_sinks :
name => sink
if sink.type == type
if sink.destination.type == type
}
}
}
resource "google_logging_folder_sink" "sink" {
for_each = var.logging_sinks
name = each.key
#description = "${each.key} (Terraform-managed)."
for_each = var.logging_sinks
name = each.key
description = coalesce(each.value.description, "${each.key} (Terraform-managed).")
folder = local.folder.name
destination = "${each.value.type}.googleapis.com/${each.value.destination}"
destination = "${each.value.destination.type}.googleapis.com/${each.value.destination.target}"
filter = each.value.filter
include_children = each.value.include_children
disabled = each.value.disabled
dynamic "bigquery_options" {
for_each = each.value.bigquery_use_partitioned_table != null ? [""] : []
content {
use_partitioned_tables = each.value.bigquery_use_partitioned_table
}
}
dynamic "exclusions" {
for_each = each.value.exclusions
@ -52,34 +60,38 @@ resource "google_logging_folder_sink" "sink" {
resource "google_storage_bucket_iam_member" "gcs-sinks-binding" {
for_each = local.sink_bindings["storage"]
bucket = each.value.destination
bucket = each.value.destination.target
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]
project = split("/", each.value.destination.target)[1]
dataset_id = split("/", each.value.destination.target)[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]
project = split("/", each.value.destination.target)[1]
topic = split("/", each.value.destination.target)[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]
project = split("/", each.value.destination.target)[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
condition {
title = "${each.key} bucket writer"
description = "Grants bucketWriter to ${google_logging_folder_sink.sink[each.key].writer_identity} used by log sink ${each.key} on ${local.folder.id}"
expression = "resource.name.endsWith('${each.value.destination.target}')"
}
}
resource "google_logging_folder_exclusion" "logging-exclusion" {

View File

@ -105,22 +105,33 @@ variable "logging_exclusions" {
variable "logging_sinks" {
description = "Logging sinks to create for this folder."
type = map(object({
destination = string
type = string
bigquery_use_partitioned_table = optional(bool)
description = optional(string)
destination = object({
type = string
target = string
})
disabled = optional(bool, false)
exclusions = optional(map(string), {})
filter = string
include_children = bool
# TODO exclusions also support description and disabled
exclusions = map(string)
include_children = optional(bool, true)
}))
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
validation {
condition = alltrue([
for k, v in var.logging_sinks :
contains(["bigquery", "logging", "pubsub", "storage"], v.destination.type)
])
error_message = "Destination type must be one of 'bigquery', 'logging', 'pubsub', 'storage'."
}
validation {
condition = alltrue([
for k, v in var.logging_sinks :
v.bigquery_use_partitioned_table != true || v.destination.type == "bigquery"
])
error_message = "Can only set bigquery_use_partitioned_table when destination type is `bigquery`."
}
}
variable "name" {

View File

@ -14,26 +14,23 @@
* limitations under the License.
*/
output "bucket" {
description = "Bucket resource."
value = google_storage_bucket.bucket
}
# We add `id` as an alias to `name` to simplify log sink handling.
# Since all other log destinations (pubsub, logging-bucket, bigquery)
# have an id output, it is convenient to have in this module too to
# handle all log destination as homogeneous objects (i.e. you can
# assume any valid log destination has an `id` output).
output "id" {
description = "Bucket ID (same as name)."
value = "${local.prefix}${lower(var.name)}"
output "as_logging_destination" {
description = "Parameters to use this bucket as a log sink destination."
value = {
type = "storage"
target = "${local.prefix}${lower(var.name)}"
}
depends_on = [
google_storage_bucket.bucket,
google_storage_bucket_iam_binding.bindings
]
}
output "bucket" {
description = "Bucket resource."
value = google_storage_bucket.bucket
}
output "name" {
description = "Bucket name."
value = "${local.prefix}${lower(var.name)}"

View File

@ -14,6 +14,19 @@
* limitations under the License.
*/
output "as_logging_destination" {
description = "Parameters to use this bucket as a log sink destination."
value = {
type = "logging"
target = try(
google_logging_project_bucket_config.bucket.0.id,
google_logging_folder_bucket_config.bucket.0.id,
google_logging_organization_bucket_config.bucket.0.id,
google_logging_billing_account_bucket_config.bucket.0.id,
)
}
}
output "id" {
description = "ID of the created bucket."
value = try(

View File

@ -311,35 +311,21 @@ module "org" {
logging_sinks = {
warnings = {
type = "storage"
destination = module.gcs.id
destination = module.gcs.as_logging_destination
filter = "severity=WARNING"
include_children = true
bq_partitioned_table = null
exclusions = {}
}
info = {
type = "bigquery"
destination = module.dataset.id
destination = module.dataset.as_logging_destination
filter = "severity=INFO"
include_children = true
bq_partitioned_table = true
exclusions = {}
bigquery_use_partitioned_table = true
}
notice = {
type = "pubsub"
destination = module.pubsub.id
destination = module.pubsub.as_logging_destination
filter = "severity=NOTICE"
include_children = true
bq_partitioned_table = null
exclusions = {}
}
debug = {
type = "logging"
destination = module.bucket.id
destination = module.bucket.as_logging_destination
filter = "severity=DEBUG"
include_children = false
bq_partitioned_table = null
exclusions = {
no-compute = "logName:compute"
}
@ -425,7 +411,7 @@ module "org" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [organization_id](variables.tf#L217) | Organization id in organizations/nnnnnn format. | <code>string</code> | ✓ | |
| [organization_id](variables.tf#L227) | Organization id in organizations/nnnnnn format. | <code>string</code> | ✓ | |
| [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. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [custom_roles](variables.tf#L24) | Map of role name => list of permissions to create in this project. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [firewall_policies](variables.tf#L31) | Hierarchical firewall policy rules created in the organization. | <code title="map&#40;map&#40;object&#40;&#123;&#10; action &#61; string&#10; description &#61; string&#10; direction &#61; string&#10; logging &#61; bool&#10; ports &#61; map&#40;list&#40;string&#41;&#41;&#10; priority &#61; number&#10; ranges &#61; list&#40;string&#41;&#10; target_resources &#61; list&#40;string&#41;&#10; target_service_accounts &#61; list&#40;string&#41;&#10;&#125;&#41;&#41;&#41;">map&#40;map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
@ -439,13 +425,13 @@ module "org" {
| [iam_audit_config_authoritative](variables.tf#L105) | IAM Authoritative service audit logging configuration. Service as key, map of log permission (eg DATA_READ) and excluded members as value for each service. Audit config should also be authoritative when using authoritative bindings. Use with caution. | <code>map&#40;map&#40;list&#40;string&#41;&#41;&#41;</code> | | <code>null</code> |
| [iam_bindings_authoritative](variables.tf#L116) | IAM authoritative bindings, in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared. Bindings should also be authoritative when using authoritative audit config. Use with caution. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>null</code> |
| [logging_exclusions](variables.tf#L122) | Logging exclusions for this organization in the form {NAME -> FILTER}. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [logging_sinks](variables.tf#L129) | Logging sinks to create for this organization. | <code title="map&#40;object&#40;&#123;&#10; destination &#61; string&#10; type &#61; string&#10; filter &#61; string&#10; include_children &#61; bool&#10; bq_partitioned_table &#61; bool&#10; exclusions &#61; map&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [org_policies](variables.tf#L151) | Organization policies applied to this organization keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; condition &#61; object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [org_policies_data_path](variables.tf#L191) | Path containing org policies in YAML format. | <code>string</code> | | <code>null</code> |
| [org_policy_custom_constraints](variables.tf#L197) | Organization policiy custom constraints keyed by constraint name. | <code title="map&#40;object&#40;&#123;&#10; display_name &#61; optional&#40;string&#41;&#10; description &#61; optional&#40;string&#41;&#10; action_type &#61; string&#10; condition &#61; string&#10; method_types &#61; list&#40;string&#41;&#10; resource_types &#61; list&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [org_policy_custom_constraints_data_path](variables.tf#L211) | Path containing org policy custom constraints in YAML format. | <code>string</code> | | <code>null</code> |
| [tag_bindings](variables.tf#L227) | Tag bindings for this organization, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> |
| [tags](variables.tf#L233) | Tags by key name. The `iam` attribute behaves like the similarly named one at module level. | <code title="map&#40;object&#40;&#123;&#10; description &#61; string&#10; iam &#61; map&#40;list&#40;string&#41;&#41;&#10; values &#61; map&#40;object&#40;&#123;&#10; description &#61; string&#10; iam &#61; map&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>null</code> |
| [logging_sinks](variables.tf#L129) | Logging sinks to create for the organization. | <code title="map&#40;object&#40;&#123;&#10; bigquery_use_partitioned_table &#61; optional&#40;bool&#41;&#10; description &#61; optional&#40;string&#41;&#10; destination &#61; object&#40;&#123;&#10; type &#61; string&#10; target &#61; string&#10; &#125;&#41;&#10; disabled &#61; optional&#40;bool, false&#41;&#10; exclusions &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; filter &#61; string&#10; include_children &#61; optional&#40;bool, true&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [org_policies](variables.tf#L161) | Organization policies applied to this organization keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; condition &#61; object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [org_policies_data_path](variables.tf#L201) | Path containing org policies in YAML format. | <code>string</code> | | <code>null</code> |
| [org_policy_custom_constraints](variables.tf#L207) | Organization policiy custom constraints keyed by constraint name. | <code title="map&#40;object&#40;&#123;&#10; display_name &#61; optional&#40;string&#41;&#10; description &#61; optional&#40;string&#41;&#10; action_type &#61; string&#10; condition &#61; string&#10; method_types &#61; list&#40;string&#41;&#10; resource_types &#61; list&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [org_policy_custom_constraints_data_path](variables.tf#L221) | Path containing org policy custom constraints in YAML format. | <code>string</code> | | <code>null</code> |
| [tag_bindings](variables.tf#L237) | Tag bindings for this organization, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> |
| [tags](variables.tf#L243) | Tags by key name. The `iam` attribute behaves like the similarly named one at module level. | <code title="map&#40;object&#40;&#123;&#10; description &#61; string&#10; iam &#61; map&#40;list&#40;string&#41;&#41;&#10; values &#61; map&#40;object&#40;&#123;&#10; description &#61; string&#10; iam &#61; map&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>null</code> |
## Outputs

View File

@ -21,7 +21,7 @@ locals {
for type in ["bigquery", "logging", "pubsub", "storage"] :
type => {
for name, sink in var.logging_sinks :
name => sink if sink.type == type
name => sink if sink.destination.type == type
}
}
}
@ -29,15 +29,17 @@ locals {
resource "google_logging_organization_sink" "sink" {
for_each = var.logging_sinks
name = each.key
description = coalesce(each.value.description, "${each.key} (Terraform-managed).")
org_id = local.organization_id_numeric
destination = "${each.value.type}.googleapis.com/${each.value.destination}"
destination = "${each.value.destination.type}.googleapis.com/${each.value.destination.target}"
filter = each.value.filter
include_children = each.value.include_children
disabled = each.value.disabled
dynamic "bigquery_options" {
for_each = each.value.bq_partitioned_table == true ? [""] : []
for_each = each.value.bigquery_use_partitioned_table != null ? [""] : []
content {
use_partitioned_tables = each.value.bq_partitioned_table
use_partitioned_tables = each.value.bigquery_use_partitioned_table
}
}
@ -49,6 +51,7 @@ resource "google_logging_organization_sink" "sink" {
filter = exclusion.value
}
}
depends_on = [
google_organization_iam_binding.authoritative,
google_organization_iam_member.additive,
@ -58,33 +61,38 @@ resource "google_logging_organization_sink" "sink" {
resource "google_storage_bucket_iam_member" "storage-sinks-binding" {
for_each = local.sink_bindings["storage"]
bucket = each.value.destination
bucket = each.value.destination.target
role = "roles/storage.objectCreator"
member = google_logging_organization_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]
project = split("/", each.value.destination.target)[1]
dataset_id = split("/", each.value.destination.target)[3]
role = "roles/bigquery.dataEditor"
member = google_logging_organization_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]
project = split("/", each.value.destination.target)[1]
topic = split("/", each.value.destination.target)[3]
role = "roles/pubsub.publisher"
member = google_logging_organization_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]
project = split("/", each.value.destination.target)[1]
role = "roles/logging.bucketWriter"
member = google_logging_organization_sink.sink[each.key].writer_identity
# TODO(jccb): use a condition to limit writer-identity only to this bucket
condition {
title = "${each.key} bucket writer"
description = "Grants bucketWriter to ${google_logging_organization_sink.sink[each.key].writer_identity} used by log sink ${each.key} on ${var.organization_id}"
expression = "resource.name.endsWith('${each.value.destination.target}')"
}
}
resource "google_logging_organization_exclusion" "logging-exclusion" {

View File

@ -127,25 +127,35 @@ variable "logging_exclusions" {
}
variable "logging_sinks" {
description = "Logging sinks to create for this organization."
description = "Logging sinks to create for the organization."
type = map(object({
destination = string
type = string
filter = string
include_children = bool
bq_partitioned_table = bool
# TODO exclusions also support description and disabled
exclusions = map(string)
bigquery_use_partitioned_table = optional(bool)
description = optional(string)
destination = object({
type = string
target = string
})
disabled = optional(bool, false)
exclusions = optional(map(string), {})
filter = string
include_children = optional(bool, true)
}))
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
validation {
condition = alltrue([
for k, v in var.logging_sinks :
contains(["bigquery", "logging", "pubsub", "storage"], v.destination.type)
])
error_message = "Destination type must be one of 'bigquery', 'logging', 'pubsub', 'storage'."
}
validation {
condition = alltrue([
for k, v in var.logging_sinks :
v.bigquery_use_partitioned_table != true || v.destination.type == "bigquery"
])
error_message = "Can only set bigquery_use_partitioned_table when destination type is `bigquery`."
}
}
variable "org_policies" {

View File

@ -276,7 +276,7 @@ compute.restrictLoadBalancerCreationForTypes:
```
## Logging Sinks
## Logging Sinks (in same project)
```hcl
module "gcs" {
@ -312,35 +312,20 @@ module "project-host" {
parent = "folders/1234567890"
logging_sinks = {
warnings = {
type = "storage"
destination = module.gcs.id
filter = "severity=WARNING"
iam = false
unique_writer = false
exclusions = {}
destination = module.gcs.as_logging_destination
filter = "severity=WARNING"
}
info = {
type = "bigquery"
destination = module.dataset.id
filter = "severity=INFO"
iam = false
unique_writer = false
exclusions = {}
destination = module.dataset.as_logging_destination
filter = "severity=INFO"
}
notice = {
type = "pubsub"
destination = module.pubsub.id
filter = "severity=NOTICE"
iam = true
unique_writer = false
exclusions = {}
destination = module.pubsub.as_logging_destination
filter = "severity=NOTICE"
}
debug = {
type = "logging"
destination = module.bucket.id
filter = "severity=DEBUG"
iam = true
unique_writer = false
destination = module.bucket.as_logging_destination
filter = "severity=DEBUG"
exclusions = {
no-compute = "logName:compute"
}
@ -350,9 +335,38 @@ module "project-host" {
no-gce-instances = "resource.type=gce_instance"
}
}
# tftest modules=5 resources=12
# tftest modules=5 resources=14
```
## Logging Sinks (in different project)
When writing to destinations in a different project, set `unique_writer` to `true`.
```hcl
module "gcs" {
source = "./fabric/modules/gcs"
project_id = "project-1"
name = "gcs_sink"
force_destroy = true
}
module "project-host" {
source = "./fabric/modules/project"
name = "project-2"
billing_account = "123456-123456-123456"
parent = "folders/1234567890"
logging_sinks = {
warnings = {
destination = module.gcs.as_logging_destination
filter = "severity=WARNING"
unique_writer = true
}
}
}
# tftest modules=2 resources=4
```
## Cloud KMS encryption keys
The module offers a simple, centralized way to assign `roles/cloudkms.cryptoKeyEncrypterDecrypter` to service identities.
@ -455,7 +469,7 @@ output "compute_robot" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [name](variables.tf#L131) | Project name and id suffix. | <code>string</code> | ✓ | |
| [name](variables.tf#L142) | Project name and id suffix. | <code>string</code> | ✓ | |
| [auto_create_network](variables.tf#L17) | Whether to create the default network for the project. | <code>bool</code> | | <code>false</code> |
| [billing_account](variables.tf#L23) | Billing account id. | <code>string</code> | | <code>null</code> |
| [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. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
@ -469,25 +483,25 @@ output "compute_robot" {
| [labels](variables.tf#L82) | Resource labels. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [lien_reason](variables.tf#L89) | If non-empty, creates a project lien with this description. | <code>string</code> | | <code>&#34;&#34;</code> |
| [logging_exclusions](variables.tf#L95) | Logging exclusions for this project in the form {NAME -> FILTER}. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [logging_sinks](variables.tf#L102) | Logging sinks to create for this project. | <code title="map&#40;object&#40;&#123;&#10; destination &#61; string&#10; type &#61; string&#10; filter &#61; string&#10; iam &#61; bool&#10; unique_writer &#61; bool&#10; exclusions &#61; map&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [metric_scopes](variables.tf#L124) | List of projects that will act as metric scopes for this project. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |
| [org_policies](variables.tf#L136) | Organization policies applied to this project keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; condition &#61; object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [org_policies_data_path](variables.tf#L176) | Path containing org policies in YAML format. | <code>string</code> | | <code>null</code> |
| [oslogin](variables.tf#L182) | Enable OS Login. | <code>bool</code> | | <code>false</code> |
| [oslogin_admins](variables.tf#L188) | List of IAM-style identities that will be granted roles necessary for OS Login administrators. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |
| [oslogin_users](variables.tf#L196) | List of IAM-style identities that will be granted roles necessary for OS Login users. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |
| [parent](variables.tf#L203) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | <code>string</code> | | <code>null</code> |
| [prefix](variables.tf#L213) | Optional prefix used to generate project id and name. | <code>string</code> | | <code>null</code> |
| [project_create](variables.tf#L223) | Create project. When set to false, uses a data source to reference existing project. | <code>bool</code> | | <code>true</code> |
| [service_config](variables.tf#L229) | Configure service API activation. | <code title="object&#40;&#123;&#10; disable_on_destroy &#61; bool&#10; disable_dependent_services &#61; bool&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; disable_on_destroy &#61; false&#10; disable_dependent_services &#61; false&#10;&#125;">&#123;&#8230;&#125;</code> |
| [service_encryption_key_ids](variables.tf#L241) | Cloud KMS encryption key in {SERVICE => [KEY_URL]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [service_perimeter_bridges](variables.tf#L248) | Name of VPC-SC Bridge perimeters to add project into. See comment in the variables file for format. | <code>list&#40;string&#41;</code> | | <code>null</code> |
| [service_perimeter_standard](variables.tf#L255) | Name of VPC-SC Standard perimeter to add project into. See comment in the variables file for format. | <code>string</code> | | <code>null</code> |
| [services](variables.tf#L261) | Service APIs to enable. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |
| [shared_vpc_host_config](variables.tf#L267) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | <code title="object&#40;&#123;&#10; enabled &#61; bool&#10; service_projects &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [shared_vpc_service_config](variables.tf#L276) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | <code title="object&#40;&#123;&#10; host_project &#61; string&#10; service_identity_iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [skip_delete](variables.tf#L286) | Allows the underlying resources to be destroyed without destroying the project itself. | <code>bool</code> | | <code>false</code> |
| [tag_bindings](variables.tf#L292) | Tag bindings for this project, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> |
| [logging_sinks](variables.tf#L102) | Logging sinks to create for this project. | <code title="map&#40;object&#40;&#123;&#10; bigquery_use_partitioned_table &#61; optional&#40;bool&#41;&#10; description &#61; optional&#40;string&#41;&#10; destination &#61; object&#40;&#123;&#10; type &#61; string&#10; target &#61; string&#10; &#125;&#41;&#10; disabled &#61; optional&#40;bool, false&#41;&#10; exclusions &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; filter &#61; string&#10; iam &#61; optional&#40;bool, true&#41;&#10; unique_writer &#61; optional&#40;bool&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [metric_scopes](variables.tf#L135) | List of projects that will act as metric scopes for this project. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |
| [org_policies](variables.tf#L147) | Organization policies applied to this project keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; condition &#61; object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [org_policies_data_path](variables.tf#L187) | Path containing org policies in YAML format. | <code>string</code> | | <code>null</code> |
| [oslogin](variables.tf#L193) | Enable OS Login. | <code>bool</code> | | <code>false</code> |
| [oslogin_admins](variables.tf#L199) | List of IAM-style identities that will be granted roles necessary for OS Login administrators. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |
| [oslogin_users](variables.tf#L207) | List of IAM-style identities that will be granted roles necessary for OS Login users. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |
| [parent](variables.tf#L214) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | <code>string</code> | | <code>null</code> |
| [prefix](variables.tf#L224) | Optional prefix used to generate project id and name. | <code>string</code> | | <code>null</code> |
| [project_create](variables.tf#L234) | Create project. When set to false, uses a data source to reference existing project. | <code>bool</code> | | <code>true</code> |
| [service_config](variables.tf#L240) | Configure service API activation. | <code title="object&#40;&#123;&#10; disable_on_destroy &#61; bool&#10; disable_dependent_services &#61; bool&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; disable_on_destroy &#61; false&#10; disable_dependent_services &#61; false&#10;&#125;">&#123;&#8230;&#125;</code> |
| [service_encryption_key_ids](variables.tf#L252) | Cloud KMS encryption key in {SERVICE => [KEY_URL]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [service_perimeter_bridges](variables.tf#L259) | Name of VPC-SC Bridge perimeters to add project into. See comment in the variables file for format. | <code>list&#40;string&#41;</code> | | <code>null</code> |
| [service_perimeter_standard](variables.tf#L266) | Name of VPC-SC Standard perimeter to add project into. See comment in the variables file for format. | <code>string</code> | | <code>null</code> |
| [services](variables.tf#L272) | Service APIs to enable. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |
| [shared_vpc_host_config](variables.tf#L278) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | <code title="object&#40;&#123;&#10; enabled &#61; bool&#10; service_projects &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [shared_vpc_service_config](variables.tf#L287) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | <code title="object&#40;&#123;&#10; host_project &#61; string&#10; service_identity_iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [skip_delete](variables.tf#L297) | Allows the underlying resources to be destroyed without destroying the project itself. | <code>bool</code> | | <code>false</code> |
| [tag_bindings](variables.tf#L303) | Tag bindings for this project, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> |
## Outputs

View File

@ -21,19 +21,27 @@ locals {
for type in ["bigquery", "pubsub", "logging", "storage"] :
type => {
for name, sink in var.logging_sinks :
name => sink if sink.iam && sink.type == type
name => sink if sink.iam && sink.destination.type == type
}
}
}
resource "google_logging_project_sink" "sink" {
for_each = var.logging_sinks
name = each.key
#description = "${each.key} (Terraform-managed)."
for_each = var.logging_sinks
name = each.key
description = coalesce(each.value.description, "${each.key} (Terraform-managed).")
project = local.project.project_id
destination = "${each.value.type}.googleapis.com/${each.value.destination}"
destination = "${each.value.destination.type}.googleapis.com/${each.value.destination.target}"
filter = each.value.filter
unique_writer_identity = each.value.unique_writer
disabled = each.value.disabled
dynamic "bigquery_options" {
for_each = each.value.bigquery_use_partitioned_table != null ? [""] : []
content {
use_partitioned_tables = each.value.bigquery_use_partitioned_table
}
}
dynamic "exclusions" {
for_each = each.value.exclusions
@ -52,34 +60,38 @@ resource "google_logging_project_sink" "sink" {
resource "google_storage_bucket_iam_member" "gcs-sinks-binding" {
for_each = local.sink_bindings["storage"]
bucket = each.value.destination
bucket = each.value.destination.target
role = "roles/storage.objectCreator"
member = google_logging_project_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]
project = split("/", each.value.destination.target)[1]
dataset_id = split("/", each.value.destination.target)[3]
role = "roles/bigquery.dataEditor"
member = google_logging_project_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]
project = split("/", each.value.destination.target)[1]
topic = split("/", each.value.destination.target)[3]
role = "roles/pubsub.publisher"
member = google_logging_project_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]
project = split("/", each.value.destination.target)[1]
role = "roles/logging.bucketWriter"
member = google_logging_project_sink.sink[each.key].writer_identity
# TODO(jccb): use a condition to limit writer-identity only to this
# bucket
condition {
title = "${each.key} bucket writer"
description = "Grants bucketWriter to ${google_logging_project_sink.sink[each.key].writer_identity} used by log sink ${each.key} on ${local.project.project_id}"
expression = "resource.name.endsWith('${each.value.destination.target}')"
}
}
resource "google_logging_project_exclusion" "logging-exclusion" {

View File

@ -102,23 +102,34 @@ variable "logging_exclusions" {
variable "logging_sinks" {
description = "Logging sinks to create for this project."
type = map(object({
destination = string
type = string
bigquery_use_partitioned_table = optional(bool)
description = optional(string)
destination = object({
type = string
target = string
})
disabled = optional(bool, false)
exclusions = optional(map(string), {})
filter = string
iam = bool
unique_writer = bool
# TODO exclusions also support description and disabled
exclusions = map(string)
iam = optional(bool, true)
unique_writer = optional(bool)
}))
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
validation {
condition = alltrue([
for k, v in var.logging_sinks :
contains(["bigquery", "logging", "pubsub", "storage"], v.destination.type)
])
error_message = "Destination type must be one of 'bigquery', 'logging', 'pubsub', 'storage'."
}
validation {
condition = alltrue([
for k, v in var.logging_sinks :
v.bigquery_use_partitioned_table != true || v.destination.type == "bigquery"
])
error_message = "Can only set bigquery_use_partitioned_table when destination type is `bigquery`."
}
}
variable "metric_scopes" {

View File

@ -14,6 +14,17 @@
* limitations under the License.
*/
output "as_logging_destination" {
description = "Parameters to use this topic as a log sink destination."
value = {
type = "pubsub"
target = google_pubsub_topic.default.id
}
depends_on = [
google_pubsub_topic_iam_binding.default
]
}
output "id" {
description = "Topic id."
value = google_pubsub_topic.default.id

View File

@ -0,0 +1,37 @@
logging_sinks = {
warning = {
destination = {
type = "storage"
target = "mybucket"
}
filter = "severity=WARNING"
}
info = {
destination = {
type = "bigquery"
target = "projects/myproject/datasets/mydataset"
}
filter = "severity=INFO"
disabled = true
}
notice = {
destination = {
type = "pubsub"
target = "projects/myproject/topics/mytopic"
}
filter = "severity=NOTICE"
include_children = false
}
debug = {
destination = {
type = "logging"
target = "projects/myproject/locations/global/buckets/mybucket"
}
filter = "severity=DEBUG"
include_children = false
exclusions = {
no-compute = "logName:compute"
no-container = "logName:container"
}
}
}

View File

@ -14,47 +14,13 @@
from collections import Counter
from icecream import ic
def test_sinks(plan_runner):
"Test folder-level sinks."
logging_sinks = """ {
warning = {
type = "storage"
destination = "mybucket"
filter = "severity=WARNING"
iam = true
include_children = true
exclusions = {}
}
info = {
type = "bigquery"
destination = "projects/myproject/datasets/mydataset"
filter = "severity=INFO"
iam = true
include_children = true
exclusions = {}
}
notice = {
type = "pubsub"
destination = "projects/myproject/topics/mytopic"
filter = "severity=NOTICE"
iam = true
include_children = false
exclusions = {}
}
debug = {
type = "logging"
destination = "projects/myproject/locations/global/buckets/mybucket"
filter = "severity=DEBUG"
iam = true
include_children = false
exclusions = {
no-compute = "logName:compute"
no-container = "logName:container"
}
}
}
"""
_, resources = plan_runner(logging_sinks=logging_sinks)
tfvars = 'test.logging-sinks.tfvars'
_, resources = plan_runner(tf_var_file=tfvars)
assert len(resources) == 9
resource_types = Counter([r["type"] for r in resources])
@ -74,65 +40,59 @@ def test_sinks(plan_runner):
"notice",
"warning",
]
values = [
(
r["index"],
r["values"]["filter"],
r["values"]["destination"],
r["values"]["include_children"],
)
for r in sinks
]
values = [(
r["index"],
r["values"]["filter"],
r["values"]["destination"],
r["values"]["description"],
r["values"]["include_children"],
r["values"]["disabled"],
) for r in sinks]
assert sorted(values) == [
(
"debug",
"severity=DEBUG",
"logging.googleapis.com/projects/myproject/locations/global/buckets/mybucket",
False,
),
(
"info",
"severity=INFO",
"bigquery.googleapis.com/projects/myproject/datasets/mydataset",
True,
),
(
"notice",
"severity=NOTICE",
"pubsub.googleapis.com/projects/myproject/topics/mytopic",
False,
),
("warning", "severity=WARNING", "storage.googleapis.com/mybucket", True),
("debug", "severity=DEBUG",
"logging.googleapis.com/projects/myproject/locations/global/buckets/mybucket",
"debug (Terraform-managed).", False, False),
("info", "severity=INFO",
"bigquery.googleapis.com/projects/myproject/datasets/mydataset",
"info (Terraform-managed).", True, True),
("notice", "severity=NOTICE",
"pubsub.googleapis.com/projects/myproject/topics/mytopic",
"notice (Terraform-managed).", False, False),
("warning", "severity=WARNING", "storage.googleapis.com/mybucket",
"warning (Terraform-managed).", True, False),
]
bindings = [r for r in resources if "member" in r["type"]]
values = [(r["index"], r["type"], r["values"]["role"]) for r in bindings]
values = [(r["index"], r["type"], r["values"]["role"],
r["values"]["condition"]) for r in bindings]
assert sorted(values) == [
("debug", "google_project_iam_member", "roles/logging.bucketWriter"),
("info", "google_bigquery_dataset_iam_member", "roles/bigquery.dataEditor"),
("notice", "google_pubsub_topic_iam_member", "roles/pubsub.publisher"),
("warning", "google_storage_bucket_iam_member", "roles/storage.objectCreator"),
("debug", "google_project_iam_member", "roles/logging.bucketWriter", [{
'expression':
"resource.name.endsWith('projects/myproject/locations/global/buckets/mybucket')",
'title':
'debug bucket writer'
}]),
("info", "google_bigquery_dataset_iam_member",
"roles/bigquery.dataEditor", []),
("notice", "google_pubsub_topic_iam_member", "roles/pubsub.publisher",
[]),
("warning", "google_storage_bucket_iam_member",
"roles/storage.objectCreator", []),
]
exclusions = [(r["index"], r["values"]["exclusions"]) for r in sinks]
assert sorted(exclusions) == [
(
"debug",
[
{
"description": None,
"disabled": False,
"filter": "logName:compute",
"name": "no-compute",
},
{
"description": None,
"disabled": False,
"filter": "logName:container",
"name": "no-container",
},
],
),
("debug", [{
"description": None,
"disabled": False,
"filter": "logName:compute",
"name": "no-compute"
}, {
"description": None,
"disabled": False,
"filter": "logName:container",
"name": "no-container"
}]),
("info", []),
("notice", []),
("warning", []),
@ -141,12 +101,10 @@ def test_sinks(plan_runner):
def test_exclusions(plan_runner):
"Test folder-level logging exclusions."
logging_exclusions = (
"{"
'exclusion1 = "resource.type=gce_instance", '
'exclusion2 = "severity=NOTICE", '
"}"
)
logging_exclusions = ("{"
'exclusion1 = "resource.type=gce_instance", '
'exclusion2 = "severity=NOTICE", '
"}")
_, resources = plan_runner(logging_exclusions=logging_exclusions)
assert len(resources) == 3
exclusions = [

View File

@ -0,0 +1,37 @@
logging_sinks = {
warning = {
destination = {
type = "storage"
target = "mybucket"
}
filter = "severity=WARNING"
}
info = {
destination = {
type = "bigquery"
target = "projects/myproject/datasets/mydataset"
}
filter = "severity=INFO"
disabled = true
}
notice = {
destination = {
type = "pubsub"
target = "projects/myproject/topics/mytopic"
}
filter = "severity=NOTICE"
include_children = false
}
debug = {
destination = {
type = "logging"
target = "projects/myproject/locations/global/buckets/mybucket"
}
filter = "severity=DEBUG"
include_children = false
exclusions = {
no-compute = "logName:compute"
no-container = "logName:container"
}
}
}

View File

@ -17,49 +17,8 @@ from collections import Counter
def test_sinks(plan_runner):
"Test folder-level sinks."
logging_sinks = """ {
warning = {
type = "storage"
destination = "mybucket"
filter = "severity=WARNING"
iam = true
include_children = true
bq_partitioned_table = null
exclusions = {}
}
info = {
type = "bigquery"
destination = "projects/myproject/datasets/mydataset"
filter = "severity=INFO"
iam = true
include_children = true
bq_partitioned_table = false
exclusions = {}
}
notice = {
type = "pubsub"
destination = "projects/myproject/topics/mytopic"
filter = "severity=NOTICE"
iam = true
include_children = false
bq_partitioned_table = null
exclusions = {}
}
debug = {
type = "logging"
destination = "projects/myproject/locations/global/buckets/mybucket"
filter = "severity=DEBUG"
iam = true
include_children = false
bq_partitioned_table = null
exclusions = {
no-compute = "logName:compute"
no-container = "logName:container"
}
}
}
"""
_, resources = plan_runner(logging_sinks=logging_sinks)
tfvars = 'test.logging-sinks.tfvars'
_, resources = plan_runner(tf_var_file=tfvars)
assert len(resources) == 8
resource_types = Counter([r["type"] for r in resources])
@ -71,23 +30,21 @@ def test_sinks(plan_runner):
"google_storage_bucket_iam_member": 1,
}
sinks = [r for r in resources if r["type"]
== "google_logging_organization_sink"]
sinks = [
r for r in resources if r["type"] == "google_logging_organization_sink"
]
assert sorted([r["index"] for r in sinks]) == [
"debug",
"info",
"notice",
"warning",
]
values = [
(
r["index"],
r["values"]["filter"],
r["values"]["destination"],
r["values"]["include_children"],
)
for r in sinks
]
values = [(
r["index"],
r["values"]["filter"],
r["values"]["destination"],
r["values"]["include_children"],
) for r in sinks]
assert sorted(values) == [
(
"debug",
@ -114,9 +71,11 @@ def test_sinks(plan_runner):
values = [(r["index"], r["type"], r["values"]["role"]) for r in bindings]
assert sorted(values) == [
("debug", "google_project_iam_member", "roles/logging.bucketWriter"),
("info", "google_bigquery_dataset_iam_member", "roles/bigquery.dataEditor"),
("info", "google_bigquery_dataset_iam_member",
"roles/bigquery.dataEditor"),
("notice", "google_pubsub_topic_iam_member", "roles/pubsub.publisher"),
("warning", "google_storage_bucket_iam_member", "roles/storage.objectCreator"),
("warning", "google_storage_bucket_iam_member",
"roles/storage.objectCreator"),
]
exclusions = [(r["index"], r["values"]["exclusions"]) for r in sinks]
@ -146,16 +105,15 @@ def test_sinks(plan_runner):
def test_exclusions(plan_runner):
"Test folder-level logging exclusions."
logging_exclusions = (
"{"
'exclusion1 = "resource.type=gce_instance", '
'exclusion2 = "severity=NOTICE", '
"}"
)
logging_exclusions = ("{"
'exclusion1 = "resource.type=gce_instance", '
'exclusion2 = "severity=NOTICE", '
"}")
_, resources = plan_runner(logging_exclusions=logging_exclusions)
assert len(resources) == 2
exclusions = [
r for r in resources if r["type"] == "google_logging_organization_exclusion"
r for r in resources
if r["type"] == "google_logging_organization_exclusion"
]
assert sorted([r["index"] for r in exclusions]) == [
"exclusion1",

View File

@ -0,0 +1,37 @@
logging_sinks = {
warning = {
destination = {
type = "storage"
target = "mybucket"
}
filter = "severity=WARNING"
}
info = {
destination = {
type = "bigquery"
target = "projects/myproject/datasets/mydataset"
}
filter = "severity=INFO"
disabled = true
}
notice = {
destination = {
type = "pubsub"
target = "projects/myproject/topics/mytopic"
}
filter = "severity=NOTICE"
unique_writer = true
}
debug = {
destination = {
type = "logging"
target = "projects/myproject/locations/global/buckets/mybucket"
}
filter = "severity=DEBUG"
exclusions = {
no-compute = "logName:compute"
no-container = "logName:container"
}
unique_writer = true
}
}

View File

@ -110,14 +110,7 @@ variable "services" {
}
variable "logging_sinks" {
type = map(object({
destination = string
type = string
filter = string
iam = bool
exclusions = map(string)
unique_writer = bool
}))
type = any
default = {}
}

View File

@ -17,45 +17,8 @@ from collections import Counter
def test_sinks(plan_runner):
"Test folder-level sinks."
logging_sinks = """ {
warning = {
type = "storage"
destination = "mybucket"
filter = "severity=WARNING"
iam = true
exclusions = {}
unique_writer = false
}
info = {
type = "bigquery"
destination = "projects/myproject/datasets/mydataset"
filter = "severity=INFO"
iam = true
exclusions = {}
unique_writer = false
}
notice = {
type = "pubsub"
destination = "projects/myproject/topics/mytopic"
filter = "severity=NOTICE"
iam = true
exclusions = {}
unique_writer = false
}
debug = {
type = "logging"
destination = "projects/myproject/locations/global/buckets/mybucket"
filter = "severity=DEBUG"
iam = true
exclusions = {
no-compute = "logName:compute"
no-container = "logName:container"
}
unique_writer = true
}
}
"""
_, resources = plan_runner(logging_sinks=logging_sinks)
tfvars = 'test.logging-sinks.tfvars'
_, resources = plan_runner(tf_var_file=tfvars)
assert len(resources) == 12
resource_types = Counter([r["type"] for r in resources])
@ -77,15 +40,12 @@ def test_sinks(plan_runner):
"notice",
"warning",
]
values = [
(
r["index"],
r["values"]["filter"],
r["values"]["destination"],
r["values"]["unique_writer_identity"],
)
for r in sinks
]
values = [(
r["index"],
r["values"]["filter"],
r["values"]["destination"],
r["values"]["unique_writer_identity"],
) for r in sinks]
assert sorted(values) == [
(
"debug",
@ -103,7 +63,7 @@ def test_sinks(plan_runner):
"notice",
"severity=NOTICE",
"pubsub.googleapis.com/projects/myproject/topics/mytopic",
False,
True,
),
("warning", "severity=WARNING", "storage.googleapis.com/mybucket", False),
]
@ -112,9 +72,11 @@ def test_sinks(plan_runner):
values = [(r["index"], r["type"], r["values"]["role"]) for r in bindings]
assert sorted(values) == [
("debug", "google_project_iam_member", "roles/logging.bucketWriter"),
("info", "google_bigquery_dataset_iam_member", "roles/bigquery.dataEditor"),
("info", "google_bigquery_dataset_iam_member",
"roles/bigquery.dataEditor"),
("notice", "google_pubsub_topic_iam_member", "roles/pubsub.publisher"),
("warning", "google_storage_bucket_iam_member", "roles/storage.objectCreator"),
("warning", "google_storage_bucket_iam_member",
"roles/storage.objectCreator"),
]
exclusions = [(r["index"], r["values"]["exclusions"]) for r in sinks]
@ -144,12 +106,10 @@ def test_sinks(plan_runner):
def test_exclusions(plan_runner):
"Test folder-level logging exclusions."
logging_exclusions = (
"{"
'exclusion1 = "resource.type=gce_instance", '
'exclusion2 = "severity=NOTICE", '
"}"
)
logging_exclusions = ("{"
'exclusion1 = "resource.type=gce_instance", '
'exclusion2 = "severity=NOTICE", '
"}")
_, resources = plan_runner(logging_exclusions=logging_exclusions)
assert len(resources) == 6
exclusions = [