Add asset_feeds to resman modules (#3658)
* Add asset_feeds to resman modules * Add examples and update readmes * Extend pubsub_topic context to project and folder modules * Use pubsub_topic context for pubsub_destination * Update readmes and add project-factory asset_feed example * Update context tests * Update schemas
This commit is contained in:
@@ -17,6 +17,7 @@ This module allows the creation and management of folders, including support for
|
||||
- [KMS Autokey](#kms-autokey)
|
||||
- [Custom Security Health Analytics Modules](#custom-security-health-analytics-modules)
|
||||
- [Custom Security Health Analytics Modules Factory](#custom-security-health-analytics-modules-factory)
|
||||
- [Cloud Asset Inventory Feeds](#cloud-asset-inventory-feeds)
|
||||
- [Tags](#tags)
|
||||
- [Files](#files)
|
||||
- [Variables](#variables)
|
||||
@@ -568,6 +569,39 @@ cloudkmKeyRotationPeriod:
|
||||
- "cloudkms.googleapis.com/CryptoKey"
|
||||
```
|
||||
|
||||
## Cloud Asset Inventory Feeds
|
||||
|
||||
Cloud Asset Inventory feeds allow you to monitor asset changes in real-time by publishing notifications to a Pub/Sub topic. Feeds configured at the folder level will monitor all resources within the folder and its subfolders.
|
||||
|
||||
```hcl
|
||||
module "pubsub" {
|
||||
source = "./fabric/modules/pubsub"
|
||||
project_id = var.project_id
|
||||
name = "folder-asset-feed"
|
||||
}
|
||||
|
||||
module "folder" {
|
||||
source = "./fabric/modules/folder"
|
||||
parent = var.folder_id
|
||||
name = "Monitored Folder"
|
||||
asset_feeds = {
|
||||
compute-instances = {
|
||||
billing_project = var.project_id
|
||||
feed_output_config = {
|
||||
pubsub_destination = {
|
||||
topic = module.pubsub.id
|
||||
}
|
||||
}
|
||||
content_type = "RESOURCE"
|
||||
asset_types = [
|
||||
"compute.googleapis.com/Instance"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
# tftest modules=2 resources=3 inventory=feeds.yaml
|
||||
```
|
||||
|
||||
## Tags
|
||||
|
||||
Refer to the [Creating and managing tags](https://cloud.google.com/resource-manager/docs/tags/tags-creating-and-managing) documentation for details on usage.
|
||||
@@ -604,6 +638,7 @@ module "folder" {
|
||||
|
||||
| name | description | resources |
|
||||
|---|---|---|
|
||||
| [assets.tf](./assets.tf) | None | <code>google_cloud_asset_folder_feed</code> |
|
||||
| [iam.tf](./iam.tf) | IAM bindings. | <code>google_folder_iam_binding</code> · <code>google_folder_iam_member</code> |
|
||||
| [logging.tf](./logging.tf) | Log sinks and supporting resources. | <code>google_bigquery_dataset_iam_member</code> · <code>google_folder_iam_audit_config</code> · <code>google_logging_folder_exclusion</code> · <code>google_logging_folder_settings</code> · <code>google_logging_folder_sink</code> · <code>google_project_iam_member</code> · <code>google_pubsub_topic_iam_member</code> · <code>google_storage_bucket_iam_member</code> |
|
||||
| [main.tf](./main.tf) | Module-level locals and resources. | <code>google_assured_workloads_workload</code> · <code>google_compute_firewall_policy_association</code> · <code>google_essential_contacts_contact</code> · <code>google_folder</code> · <code>google_kms_autokey_config</code> |
|
||||
@@ -624,31 +659,32 @@ module "folder" {
|
||||
|
||||
| name | description | type | required | default |
|
||||
|---|---|:---:|:---:|:---:|
|
||||
| [assured_workload_config](variables.tf#L17) | Create AssuredWorkloads folder instead of regular folder when value is provided. Incompatible with folder_create=false. | <code title="object({ compliance_regime = string display_name = string location = string organization = string enable_sovereign_controls = optional(bool) labels = optional(map(string), {}) partner = optional(string) partner_permissions = optional(object({ assured_workloads_monitoring = optional(bool) data_logs_viewer = optional(bool) service_access_approver = optional(bool) })) violation_notifications_enabled = optional(bool) })">object({…})</code> | | <code>null</code> |
|
||||
| [autokey_config](variables.tf#L70) | Enable autokey support for this folder's children. Project accepts either project id or number. | <code title="object({ project = string })">object({…})</code> | | <code>null</code> |
|
||||
| [contacts](variables.tf#L79) | 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(list(string))</code> | | <code>{}</code> |
|
||||
| [context](variables.tf#L98) | Context-specific interpolations. | <code title="object({ condition_vars = optional(map(map(string)), {}) custom_roles = optional(map(string), {}) email_addresses = optional(map(string), {}) folder_ids = optional(map(string), {}) iam_principals = optional(map(string), {}) project_ids = optional(map(string), {}) project_numbers = optional(map(string), {}) tag_values = optional(map(string), {}) })">object({…})</code> | | <code>{}</code> |
|
||||
| [deletion_protection](variables.tf#L114) | Deletion protection setting for this folder. | <code>bool</code> | | <code>false</code> |
|
||||
| [factories_config](variables.tf#L120) | Paths to data files and folders that enable factory functionality. | <code title="object({ org_policies = optional(string) pam_entitlements = optional(string) scc_sha_custom_modules = optional(string) })">object({…})</code> | | <code>{}</code> |
|
||||
| [firewall_policy](variables.tf#L131) | Hierarchical firewall policy to associate to this folder. | <code title="object({ name = string policy = string })">object({…})</code> | | <code>null</code> |
|
||||
| [folder_create](variables.tf#L140) | Create folder. When set to false, uses id to reference an existing folder. | <code>bool</code> | | <code>true</code> |
|
||||
| [asset_feeds](variables.tf#L18) | Cloud Asset Inventory feeds. | <code title="map(object({ billing_project = string content_type = optional(string) asset_types = optional(list(string)) asset_names = optional(list(string)) feed_output_config = object({ pubsub_destination = object({ topic = string }) }) condition = optional(object({ expression = string title = optional(string) description = optional(string) location = optional(string) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [assured_workload_config](variables.tf#L51) | Create AssuredWorkloads folder instead of regular folder when value is provided. Incompatible with folder_create=false. | <code title="object({ compliance_regime = string display_name = string location = string organization = string enable_sovereign_controls = optional(bool) labels = optional(map(string), {}) partner = optional(string) partner_permissions = optional(object({ assured_workloads_monitoring = optional(bool) data_logs_viewer = optional(bool) service_access_approver = optional(bool) })) violation_notifications_enabled = optional(bool) })">object({…})</code> | | <code>null</code> |
|
||||
| [autokey_config](variables.tf#L104) | Enable autokey support for this folder's children. Project accepts either project id or number. | <code title="object({ project = string })">object({…})</code> | | <code>null</code> |
|
||||
| [contacts](variables.tf#L113) | 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(list(string))</code> | | <code>{}</code> |
|
||||
| [context](variables.tf#L132) | Context-specific interpolations. | <code title="object({ bigquery_datasets = optional(map(string), {}) condition_vars = optional(map(map(string)), {}) custom_roles = optional(map(string), {}) email_addresses = optional(map(string), {}) folder_ids = optional(map(string), {}) iam_principals = optional(map(string), {}) log_buckets = optional(map(string), {}) project_ids = optional(map(string), {}) project_numbers = optional(map(string), {}) pubsub_topics = optional(map(string), {}) storage_buckets = optional(map(string), {}) tag_values = optional(map(string), {}) })">object({…})</code> | | <code>{}</code> |
|
||||
| [deletion_protection](variables.tf#L152) | Deletion protection setting for this folder. | <code>bool</code> | | <code>false</code> |
|
||||
| [factories_config](variables.tf#L158) | Paths to data files and folders that enable factory functionality. | <code title="object({ org_policies = optional(string) pam_entitlements = optional(string) scc_sha_custom_modules = optional(string) })">object({…})</code> | | <code>{}</code> |
|
||||
| [firewall_policy](variables.tf#L169) | Hierarchical firewall policy to associate to this folder. | <code title="object({ name = string policy = string })">object({…})</code> | | <code>null</code> |
|
||||
| [folder_create](variables.tf#L178) | Create folder. When set to false, uses id to reference an existing folder. | <code>bool</code> | | <code>true</code> |
|
||||
| [iam](variables-iam.tf#L17) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_bindings](variables-iam.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | <code title="map(object({ members = list(string) role = string condition = optional(object({ expression = string title = string description = optional(string) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [iam_bindings_additive](variables-iam.tf#L39) | Individual additive IAM bindings. Keys are arbitrary. | <code title="map(object({ member = string role = string condition = optional(object({ expression = string title = string description = optional(string) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [iam_by_principals](variables-iam.tf#L61) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam` variable. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_by_principals_additive](variables-iam.tf#L54) | Additive IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam_bindings_additive` variable. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_by_principals_conditional](variables-iam.tf#L68) | Authoritative IAM binding in {PRINCIPAL => {roles = [roles], condition = {cond}}} format. Principals need to be statically defined to avoid errors. Condition is required. | <code title="map(object({ roles = list(string) condition = object({ expression = string title = string description = optional(string) }) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [id](variables.tf#L150) | Folder ID in case you use folder_create=false. | <code>string</code> | | <code>null</code> |
|
||||
| [id](variables.tf#L188) | Folder ID in case you use folder_create=false. | <code>string</code> | | <code>null</code> |
|
||||
| [logging_data_access](variables-logging.tf#L17) | Control activation of data access logs. The special 'allServices' key denotes configuration for all services. | <code title="map(object({ ADMIN_READ = optional(object({ exempted_members = optional(list(string), []) })), DATA_READ = optional(object({ exempted_members = optional(list(string), []) })), DATA_WRITE = optional(object({ exempted_members = optional(list(string), []) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [logging_exclusions](variables-logging.tf#L28) | Logging exclusions for this folder in the form {NAME -> FILTER}. | <code>map(string)</code> | | <code>{}</code> |
|
||||
| [logging_settings](variables-logging.tf#L35) | Default settings for logging resources. | <code title="object({ disable_default_sink = optional(bool) storage_location = optional(string) })">object({…})</code> | | <code>null</code> |
|
||||
| [logging_sinks](variables-logging.tf#L45) | Logging sinks to create for the folder. | <code title="map(object({ bq_partitioned_table = optional(bool, false) description = optional(string) destination = string disabled = optional(bool, false) exclusions = optional(map(string), {}) filter = optional(string) iam = optional(bool, true) include_children = optional(bool, true) intercept_children = optional(bool, false) type = string }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [name](variables.tf#L156) | Folder name. | <code>string</code> | | <code>null</code> |
|
||||
| [org_policies](variables.tf#L162) | Organization policies applied to this folder keyed by policy name. | <code title="map(object({ inherit_from_parent = optional(bool) # for list policies only. reset = optional(bool) rules = optional(list(object({ allow = optional(object({ all = optional(bool) values = optional(list(string)) })) deny = optional(object({ all = optional(bool) values = optional(list(string)) })) enforce = optional(bool) # for boolean policies only. condition = optional(object({ description = optional(string) expression = optional(string) location = optional(string) title = optional(string) }), {}) parameters = optional(string) })), []) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [name](variables.tf#L194) | Folder name. | <code>string</code> | | <code>null</code> |
|
||||
| [org_policies](variables.tf#L200) | Organization policies applied to this folder keyed by policy name. | <code title="map(object({ inherit_from_parent = optional(bool) # for list policies only. reset = optional(bool) rules = optional(list(object({ allow = optional(object({ all = optional(bool) values = optional(list(string)) })) deny = optional(object({ all = optional(bool) values = optional(list(string)) })) enforce = optional(bool) # for boolean policies only. condition = optional(object({ description = optional(string) expression = optional(string) location = optional(string) title = optional(string) }), {}) parameters = optional(string) })), []) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [pam_entitlements](variables-pam.tf#L17) | Privileged Access Manager entitlements for this resource, keyed by entitlement ID. | <code title="map(object({ max_request_duration = string eligible_users = list(string) privileged_access = list(object({ role = string condition = optional(string) })) requester_justification_config = optional(object({ not_mandatory = optional(bool, true) unstructured = optional(bool, false) }), { not_mandatory = false, unstructured = true }) manual_approvals = optional(object({ require_approver_justification = bool steps = list(object({ approvers = list(string) approvals_needed = optional(number, 1) approver_email_recipients = optional(list(string)) })) })) additional_notification_targets = optional(object({ admin_email_recipients = optional(list(string)) requester_email_recipients = optional(list(string)) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [parent](variables.tf#L190) | Parent in folders/folder_id or organizations/org_id format. | <code>string</code> | | <code>null</code> |
|
||||
| [parent](variables.tf#L228) | Parent in folders/folder_id or organizations/org_id format. | <code>string</code> | | <code>null</code> |
|
||||
| [scc_sha_custom_modules](variables-scc.tf#L17) | SCC custom modules keyed by module name. | <code title="map(object({ description = optional(string) severity = string recommendation = string predicate = object({ expression = string }) resource_selector = object({ resource_types = list(string) }) enablement_state = optional(string, "ENABLED") }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [tag_bindings](variables.tf#L204) | Tag bindings for this folder, in key => tag value id format. | <code>map(string)</code> | | <code>null</code> |
|
||||
| [tag_bindings](variables.tf#L242) | Tag bindings for this folder, in key => tag value id format. | <code>map(string)</code> | | <code>null</code> |
|
||||
|
||||
## Outputs
|
||||
|
||||
|
||||
46
modules/folder/assets.tf
Normal file
46
modules/folder/assets.tf
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright 2026 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.
|
||||
*/
|
||||
|
||||
resource "google_cloud_asset_folder_feed" "default" {
|
||||
for_each = var.asset_feeds
|
||||
billing_project = each.value.billing_project
|
||||
folder = local.folder_id
|
||||
feed_id = each.key
|
||||
content_type = each.value.content_type
|
||||
|
||||
asset_types = each.value.asset_types
|
||||
asset_names = each.value.asset_names
|
||||
|
||||
feed_output_config {
|
||||
pubsub_destination {
|
||||
topic = lookup(
|
||||
local.ctx.pubsub_topics,
|
||||
each.value.feed_output_config.pubsub_destination.topic,
|
||||
each.value.feed_output_config.pubsub_destination.topic
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
dynamic "condition" {
|
||||
for_each = each.value.condition == null ? [] : [each.value.condition]
|
||||
content {
|
||||
expression = condition.value.expression
|
||||
title = condition.value.title
|
||||
description = condition.value.description
|
||||
location = condition.value.location
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,18 +19,39 @@
|
||||
locals {
|
||||
logging_sinks = {
|
||||
for k, v in var.logging_sinks :
|
||||
# rewrite destination and type when type="project"
|
||||
k => merge(v, v.type != "project" ? {} : {
|
||||
destination = "projects/${v.destination}"
|
||||
type = "logging"
|
||||
})
|
||||
# expand destination contexts
|
||||
k => merge(v,
|
||||
v.type != "bigquery" ? {} : {
|
||||
destination = lookup(
|
||||
local.ctx.bigquery_datasets, v.destination, v.destination
|
||||
)
|
||||
},
|
||||
v.type != "logging" ? {} : {
|
||||
destination = lookup(
|
||||
local.ctx.log_buckets, v.destination, v.destination
|
||||
)
|
||||
},
|
||||
v.type != "project" ? {} : {
|
||||
api = "logging"
|
||||
destination = "projects/${lookup(local.ctx.project_ids, v.destination, v.destination)}"
|
||||
},
|
||||
v.type != "pubsub" ? {} : {
|
||||
destination = lookup(
|
||||
local.ctx.pubsub_topics, v.destination, v.destination
|
||||
)
|
||||
},
|
||||
v.type != "storage" ? {} : {
|
||||
destination = lookup(
|
||||
local.ctx.storage_buckets, v.destination, v.destination
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
sink_bindings = {
|
||||
for type in ["bigquery", "logging", "project", "pubsub", "storage"] :
|
||||
type => {
|
||||
for name, sink in var.logging_sinks :
|
||||
name => sink
|
||||
if sink.iam == true && sink.type == type
|
||||
for name, sink in local.logging_sinks :
|
||||
name => sink if sink.iam && sink.type == type
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,7 +84,7 @@ resource "google_logging_folder_sink" "sink" {
|
||||
name = each.key
|
||||
description = coalesce(each.value.description, "${each.key} (Terraform-managed).")
|
||||
folder = local.folder_id
|
||||
destination = "${each.value.type}.googleapis.com/${each.value.destination}"
|
||||
destination = "${lookup(each.value, "api", each.value.type)}.googleapis.com/${each.value.destination}"
|
||||
filter = each.value.filter
|
||||
include_children = each.value.include_children
|
||||
intercept_children = each.value.intercept_children
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
* Copyright 2026 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,6 +14,40 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
variable "asset_feeds" {
|
||||
description = "Cloud Asset Inventory feeds."
|
||||
type = map(object({
|
||||
billing_project = string
|
||||
content_type = optional(string)
|
||||
asset_types = optional(list(string))
|
||||
asset_names = optional(list(string))
|
||||
feed_output_config = object({
|
||||
pubsub_destination = object({
|
||||
topic = string
|
||||
})
|
||||
})
|
||||
condition = optional(object({
|
||||
expression = string
|
||||
title = optional(string)
|
||||
description = optional(string)
|
||||
location = optional(string)
|
||||
}))
|
||||
}))
|
||||
default = {}
|
||||
nullable = false
|
||||
validation {
|
||||
condition = alltrue([
|
||||
for k, v in var.asset_feeds :
|
||||
v.content_type == null || contains(
|
||||
["RESOURCE", "IAM_POLICY", "ORG_POLICY", "ACCESS_POLICY", "OS_INVENTORY", "RELATIONSHIP"],
|
||||
v.content_type
|
||||
)
|
||||
])
|
||||
error_message = "Content type must be one of RESOURCE, IAM_POLICY, ORG_POLICY, ACCESS_POLICY, OS_INVENTORY, RELATIONSHIP."
|
||||
}
|
||||
}
|
||||
|
||||
variable "assured_workload_config" {
|
||||
description = "Create AssuredWorkloads folder instead of regular folder when value is provided. Incompatible with folder_create=false."
|
||||
type = object({
|
||||
@@ -98,14 +132,18 @@ variable "contacts" {
|
||||
variable "context" {
|
||||
description = "Context-specific interpolations."
|
||||
type = object({
|
||||
condition_vars = optional(map(map(string)), {})
|
||||
custom_roles = optional(map(string), {})
|
||||
email_addresses = optional(map(string), {})
|
||||
folder_ids = optional(map(string), {})
|
||||
iam_principals = optional(map(string), {})
|
||||
project_ids = optional(map(string), {})
|
||||
project_numbers = optional(map(string), {})
|
||||
tag_values = optional(map(string), {})
|
||||
bigquery_datasets = optional(map(string), {})
|
||||
condition_vars = optional(map(map(string)), {})
|
||||
custom_roles = optional(map(string), {})
|
||||
email_addresses = optional(map(string), {})
|
||||
folder_ids = optional(map(string), {})
|
||||
iam_principals = optional(map(string), {})
|
||||
log_buckets = optional(map(string), {})
|
||||
project_ids = optional(map(string), {})
|
||||
project_numbers = optional(map(string), {})
|
||||
pubsub_topics = optional(map(string), {})
|
||||
storage_buckets = optional(map(string), {})
|
||||
tag_values = optional(map(string), {})
|
||||
})
|
||||
default = {}
|
||||
nullable = false
|
||||
|
||||
@@ -31,6 +31,7 @@ To manage organization policies, the `orgpolicy.googleapis.com` service should b
|
||||
- [Custom Roles Factory](#custom-roles-factory)
|
||||
- [Custom Security Health Analytics Modules](#custom-security-health-analytics-modules)
|
||||
- [Custom Security Health Analytics Modules Factory](#custom-security-health-analytics-modules-factory)
|
||||
- [Cloud Asset Inventory Feeds](#cloud-asset-inventory-feeds)
|
||||
- [Tags](#tags)
|
||||
- [Tags Factory](#tags-factory)
|
||||
- [Workforce Identity](#workforce-identity)
|
||||
@@ -578,6 +579,35 @@ cloudkmKeyRotationPeriod:
|
||||
- "cloudkms.googleapis.com/CryptoKey"
|
||||
```
|
||||
|
||||
## Cloud Asset Inventory Feeds
|
||||
|
||||
Cloud Asset Inventory feeds allow you to monitor asset changes in real-time by publishing notifications to a Pub/Sub topic. Feeds configured at the organization level will monitor all resources within the organization.
|
||||
|
||||
```hcl
|
||||
module "pubsub" {
|
||||
source = "./fabric/modules/pubsub"
|
||||
project_id = var.project_id
|
||||
name = "org-asset-feed"
|
||||
}
|
||||
|
||||
module "org" {
|
||||
source = "./fabric/modules/organization"
|
||||
organization_id = var.organization_id
|
||||
asset_feeds = {
|
||||
security-monitoring = {
|
||||
billing_project = var.project_id
|
||||
feed_output_config = {
|
||||
pubsub_destination = {
|
||||
topic = module.pubsub.id
|
||||
}
|
||||
}
|
||||
content_type = "IAM_POLICY"
|
||||
}
|
||||
}
|
||||
}
|
||||
# tftest inventory=feeds.yaml
|
||||
```
|
||||
|
||||
## Tags
|
||||
|
||||
Refer to the [Creating and managing tags](https://cloud.google.com/resource-manager/docs/tags/tags-creating-and-managing) documentation for details on usage.
|
||||
@@ -808,6 +838,7 @@ module "org" {
|
||||
|
||||
| name | description | resources |
|
||||
|---|---|---|
|
||||
| [assets.tf](./assets.tf) | None | <code>google_cloud_asset_organization_feed</code> |
|
||||
| [iam.tf](./iam.tf) | IAM bindings. | <code>google_organization_iam_binding</code> · <code>google_organization_iam_custom_role</code> · <code>google_organization_iam_member</code> |
|
||||
| [identity-providers.tf](./identity-providers.tf) | Workforce Identity Federation provider definitions. | <code>google_iam_workforce_pool</code> · <code>google_iam_workforce_pool_provider</code> |
|
||||
| [logging.tf](./logging.tf) | Log sinks and data access logs. | <code>google_bigquery_dataset_iam_member</code> · <code>google_logging_organization_exclusion</code> · <code>google_logging_organization_settings</code> · <code>google_logging_organization_sink</code> · <code>google_organization_iam_audit_config</code> · <code>google_project_iam_member</code> · <code>google_pubsub_topic_iam_member</code> · <code>google_storage_bucket_iam_member</code> |
|
||||
@@ -832,12 +863,13 @@ module "org" {
|
||||
|
||||
| name | description | type | required | default |
|
||||
|---|---|:---:|:---:|:---:|
|
||||
| [organization_id](variables.tf#L127) | 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(list(string))</code> | | <code>{}</code> |
|
||||
| [context](variables.tf#L35) | Context-specific interpolations. | <code title="object({ bigquery_datasets = optional(map(string), {}) condition_vars = optional(map(map(string)), {}) custom_roles = optional(map(string), {}) email_addresses = optional(map(string), {}) iam_principals = optional(map(string), {}) locations = optional(map(string), {}) log_buckets = optional(map(string), {}) project_ids = optional(map(string), {}) pubsub_topics = optional(map(string), {}) storage_buckets = optional(map(string), {}) tag_keys = optional(map(string), {}) tag_values = optional(map(string), {}) })">object({…})</code> | | <code>{}</code> |
|
||||
| [custom_roles](variables.tf#L55) | Map of role name => list of permissions to create in this project. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [factories_config](variables.tf#L62) | Paths to data files and folders that enable factory functionality. | <code title="object({ custom_roles = optional(string) org_policies = optional(string) org_policy_custom_constraints = optional(string) pam_entitlements = optional(string) scc_sha_custom_modules = optional(string) tags = optional(string) })">object({…})</code> | | <code>{}</code> |
|
||||
| [firewall_policy](variables.tf#L76) | Hierarchical firewall policies to associate to the organization. | <code title="object({ name = string policy = string })">object({…})</code> | | <code>null</code> |
|
||||
| [organization_id](variables.tf#L161) | Organization id in organizations/nnnnnn format. | <code>string</code> | ✓ | |
|
||||
| [asset_feeds](variables.tf#L18) | Cloud Asset Inventory feeds. | <code title="map(object({ billing_project = string content_type = optional(string) asset_types = optional(list(string)) asset_names = optional(list(string)) feed_output_config = object({ pubsub_destination = object({ topic = string }) }) condition = optional(object({ expression = string title = optional(string) description = optional(string) location = optional(string) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [contacts](variables.tf#L51) | 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(list(string))</code> | | <code>{}</code> |
|
||||
| [context](variables.tf#L69) | Context-specific interpolations. | <code title="object({ bigquery_datasets = optional(map(string), {}) condition_vars = optional(map(map(string)), {}) custom_roles = optional(map(string), {}) email_addresses = optional(map(string), {}) iam_principals = optional(map(string), {}) locations = optional(map(string), {}) log_buckets = optional(map(string), {}) project_ids = optional(map(string), {}) pubsub_topics = optional(map(string), {}) storage_buckets = optional(map(string), {}) tag_keys = optional(map(string), {}) tag_values = optional(map(string), {}) })">object({…})</code> | | <code>{}</code> |
|
||||
| [custom_roles](variables.tf#L89) | Map of role name => list of permissions to create in this project. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [factories_config](variables.tf#L96) | Paths to data files and folders that enable factory functionality. | <code title="object({ custom_roles = optional(string) org_policies = optional(string) org_policy_custom_constraints = optional(string) pam_entitlements = optional(string) scc_sha_custom_modules = optional(string) tags = optional(string) })">object({…})</code> | | <code>{}</code> |
|
||||
| [firewall_policy](variables.tf#L110) | Hierarchical firewall policies to associate to the organization. | <code title="object({ name = string policy = string })">object({…})</code> | | <code>null</code> |
|
||||
| [iam](variables-iam.tf#L17) | Authoritative IAM bindings in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_bindings](variables-iam.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | <code title="map(object({ members = list(string) role = string condition = optional(object({ expression = string title = string description = optional(string) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [iam_bindings_additive](variables-iam.tf#L39) | Individual additive IAM bindings. Keys are arbitrary. | <code title="map(object({ member = string role = string condition = optional(object({ expression = string title = string description = optional(string) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
@@ -849,8 +881,8 @@ module "org" {
|
||||
| [logging_settings](variables-logging.tf#L35) | Default settings for logging resources. | <code title="object({ disable_default_sink = optional(bool) kms_key_name = optional(string) storage_location = optional(string) })">object({…})</code> | | <code>null</code> |
|
||||
| [logging_sinks](variables-logging.tf#L46) | Logging sinks to create for the organization. | <code title="map(object({ destination = string bq_partitioned_table = optional(bool, false) description = optional(string) disabled = optional(bool, false) exclusions = optional(map(string), {}) filter = optional(string) iam = optional(bool, true) include_children = optional(bool, true) intercept_children = optional(bool, false) type = optional(string, "logging") }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [network_tags](variables-tags.tf#L17) | Network tags by key name. If `id` is provided, key creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | <code title="map(object({ description = optional(string, "Managed by the Terraform organization module.") id = optional(string) network = string # project_id/vpc_name or "ALL" to toggle GCE_FIREWALL purpose iam = optional(map(list(string)), {}) iam_bindings = optional(map(object({ members = list(string) role = string condition = optional(object({ expression = string title = string description = optional(string) })) })), {}) iam_bindings_additive = optional(map(object({ member = string role = string condition = optional(object({ expression = string title = string description = optional(string) })) })), {}) values = optional(map(object({ description = optional(string, "Managed by the Terraform organization module.") id = optional(string) iam = optional(map(list(string)), {}) iam_bindings = optional(map(object({ members = list(string) role = string condition = optional(object({ expression = string title = string description = optional(string) })) })), {}) iam_bindings_additive = optional(map(object({ member = string role = string condition = optional(object({ expression = string title = string description = optional(string) })) })), {}) })), {}) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [org_policies](variables.tf#L85) | Organization policies applied to this organization keyed by policy name. | <code title="map(object({ inherit_from_parent = optional(bool) # for list policies only. reset = optional(bool) rules = optional(list(object({ allow = optional(object({ all = optional(bool) values = optional(list(string)) })) deny = optional(object({ all = optional(bool) values = optional(list(string)) })) enforce = optional(bool) # for boolean policies only. condition = optional(object({ description = optional(string) expression = optional(string) location = optional(string) title = optional(string) }), {}) parameters = optional(string) })), []) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [org_policy_custom_constraints](variables.tf#L113) | Organization policy custom constraints keyed by constraint name. | <code title="map(object({ display_name = optional(string) description = optional(string) action_type = string condition = string method_types = list(string) resource_types = list(string) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [org_policies](variables.tf#L119) | Organization policies applied to this organization keyed by policy name. | <code title="map(object({ inherit_from_parent = optional(bool) # for list policies only. reset = optional(bool) rules = optional(list(object({ allow = optional(object({ all = optional(bool) values = optional(list(string)) })) deny = optional(object({ all = optional(bool) values = optional(list(string)) })) enforce = optional(bool) # for boolean policies only. condition = optional(object({ description = optional(string) expression = optional(string) location = optional(string) title = optional(string) }), {}) parameters = optional(string) })), []) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [org_policy_custom_constraints](variables.tf#L147) | Organization policy custom constraints keyed by constraint name. | <code title="map(object({ display_name = optional(string) description = optional(string) action_type = string condition = string method_types = list(string) resource_types = list(string) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [pam_entitlements](variables-pam.tf#L17) | Privileged Access Manager entitlements for this resource, keyed by entitlement ID. | <code title="map(object({ max_request_duration = string eligible_users = list(string) privileged_access = list(object({ role = string condition = optional(string) })) requester_justification_config = optional(object({ not_mandatory = optional(bool, true) unstructured = optional(bool, false) }), { not_mandatory = false, unstructured = true }) manual_approvals = optional(object({ require_approver_justification = bool steps = list(object({ approvers = list(string) approvals_needed = optional(number, 1) approver_email_recipients = optional(list(string)) })) })) additional_notification_targets = optional(object({ admin_email_recipients = optional(list(string)) requester_email_recipients = optional(list(string)) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [scc_sha_custom_modules](variables-scc.tf#L17) | SCC custom modules keyed by module name. | <code title="map(object({ description = optional(string) severity = string recommendation = string predicate = object({ expression = string }) resource_selector = object({ resource_types = list(string) }) enablement_state = optional(string, "ENABLED") }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [tag_bindings](variables-tags.tf#L89) | Tag bindings for this organization, in key => tag value id format. | <code>map(string)</code> | | <code>{}</code> |
|
||||
|
||||
46
modules/organization/assets.tf
Normal file
46
modules/organization/assets.tf
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright 2026 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.
|
||||
*/
|
||||
|
||||
resource "google_cloud_asset_organization_feed" "default" {
|
||||
for_each = var.asset_feeds
|
||||
billing_project = each.value.billing_project
|
||||
org_id = var.organization_id
|
||||
feed_id = each.key
|
||||
content_type = each.value.content_type
|
||||
|
||||
asset_types = each.value.asset_types
|
||||
asset_names = each.value.asset_names
|
||||
|
||||
feed_output_config {
|
||||
pubsub_destination {
|
||||
topic = lookup(
|
||||
local.ctx.pubsub_topics,
|
||||
each.value.feed_output_config.pubsub_destination.topic,
|
||||
each.value.feed_output_config.pubsub_destination.topic
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
dynamic "condition" {
|
||||
for_each = each.value.condition == null ? [] : [each.value.condition]
|
||||
content {
|
||||
expression = condition.value.expression
|
||||
title = condition.value.title
|
||||
description = condition.value.description
|
||||
location = condition.value.location
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,40 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
variable "asset_feeds" {
|
||||
description = "Cloud Asset Inventory feeds."
|
||||
type = map(object({
|
||||
billing_project = string
|
||||
content_type = optional(string)
|
||||
asset_types = optional(list(string))
|
||||
asset_names = optional(list(string))
|
||||
feed_output_config = object({
|
||||
pubsub_destination = object({
|
||||
topic = string
|
||||
})
|
||||
})
|
||||
condition = optional(object({
|
||||
expression = string
|
||||
title = optional(string)
|
||||
description = optional(string)
|
||||
location = optional(string)
|
||||
}))
|
||||
}))
|
||||
default = {}
|
||||
nullable = false
|
||||
validation {
|
||||
condition = alltrue([
|
||||
for k, v in var.asset_feeds :
|
||||
v.content_type == null || contains(
|
||||
["RESOURCE", "IAM_POLICY", "ORG_POLICY", "ACCESS_POLICY", "OS_INVENTORY", "RELATIONSHIP"],
|
||||
v.content_type
|
||||
)
|
||||
])
|
||||
error_message = "Content type must be one of RESOURCE, IAM_POLICY, ORG_POLICY, ACCESS_POLICY, OS_INVENTORY, RELATIONSHIP."
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -58,6 +58,7 @@ module "folder-1" {
|
||||
for k, v in local.folders_input : k => v if v.level == 1
|
||||
}
|
||||
deletion_protection = lookup(each.value, "deletion_protection", false)
|
||||
asset_feeds = lookup(each.value, "asset_feeds", {})
|
||||
parent = coalesce(each.value.parent, "$folder_ids:default")
|
||||
name = each.value.name
|
||||
factories_config = {
|
||||
@@ -101,6 +102,7 @@ module "folder-2" {
|
||||
for k, v in local.folders_input : k => v if v.level == 2
|
||||
}
|
||||
deletion_protection = lookup(each.value, "deletion_protection", false)
|
||||
asset_feeds = lookup(each.value, "asset_feeds", {})
|
||||
parent = coalesce(
|
||||
each.value.parent, "$folder_ids:${each.value.parent_key}"
|
||||
)
|
||||
@@ -154,6 +156,7 @@ module "folder-3" {
|
||||
for k, v in local.folders_input : k => v if v.level == 3
|
||||
}
|
||||
deletion_protection = lookup(each.value, "deletion_protection", false)
|
||||
asset_feeds = lookup(each.value, "asset_feeds", {})
|
||||
parent = coalesce(
|
||||
each.value.parent, "$folder_ids:${each.value.parent_key}"
|
||||
)
|
||||
@@ -207,6 +210,7 @@ module "folder-4" {
|
||||
for k, v in local.folders_input : k => v if v.level == 4
|
||||
}
|
||||
deletion_protection = lookup(each.value, "deletion_protection", false)
|
||||
asset_feeds = lookup(each.value, "asset_feeds", {})
|
||||
parent = coalesce(
|
||||
each.value.parent, "$folder_ids:${each.value.parent_key}"
|
||||
)
|
||||
|
||||
@@ -34,6 +34,7 @@ locals {
|
||||
# set data_overrides.<field> to "", [] or {} to ensure, that empty value is always passed, or do
|
||||
# the same in _projects_input to prevent falling back to default value
|
||||
for k, v in local._projects_input : k => merge(v, {
|
||||
asset_feeds = try(v.asset_feeds, {})
|
||||
billing_account = try(coalesce( # type: string
|
||||
local.data_defaults.overrides.billing_account,
|
||||
try(v.billing_account, null),
|
||||
|
||||
@@ -87,6 +87,7 @@ module "projects" {
|
||||
prefix = each.value.prefix
|
||||
project_reuse = each.value.project_reuse
|
||||
alerts = try(each.value.alerts, null)
|
||||
asset_feeds = each.value.asset_feeds
|
||||
auto_create_network = try(each.value.auto_create_network, false)
|
||||
compute_metadata = try(each.value.compute_metadata, {})
|
||||
# TODO: concat lists for each key
|
||||
|
||||
@@ -4,6 +4,90 @@
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"asset_feeds": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {
|
||||
"^[a-z0-9-]+$": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"billing_project",
|
||||
"feed_output_config"
|
||||
],
|
||||
"properties": {
|
||||
"billing_project": {
|
||||
"type": "string"
|
||||
},
|
||||
"content_type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"RESOURCE",
|
||||
"IAM_POLICY",
|
||||
"ORG_POLICY",
|
||||
"ACCESS_POLICY",
|
||||
"OS_INVENTORY",
|
||||
"RELATIONSHIP"
|
||||
]
|
||||
},
|
||||
"asset_types": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"asset_names": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"feed_output_config": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"pubsub_destination"
|
||||
],
|
||||
"properties": {
|
||||
"pubsub_destination": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"topic"
|
||||
],
|
||||
"properties": {
|
||||
"topic": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"condition": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"expression"
|
||||
],
|
||||
"properties": {
|
||||
"expression": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"location": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"automation": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
|
||||
@@ -4,6 +4,89 @@
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"asset_feeds": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {
|
||||
"^[a-z0-9-]+$": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"feed_output_config"
|
||||
],
|
||||
"properties": {
|
||||
"billing_project": {
|
||||
"type": "string"
|
||||
},
|
||||
"content_type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"RESOURCE",
|
||||
"IAM_POLICY",
|
||||
"ORG_POLICY",
|
||||
"ACCESS_POLICY",
|
||||
"OS_INVENTORY",
|
||||
"RELATIONSHIP"
|
||||
]
|
||||
},
|
||||
"asset_types": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"asset_names": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"feed_output_config": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"pubsub_destination"
|
||||
],
|
||||
"properties": {
|
||||
"pubsub_destination": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"topic"
|
||||
],
|
||||
"properties": {
|
||||
"topic": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"condition": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"expression"
|
||||
],
|
||||
"properties": {
|
||||
"expression": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"location": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"automation": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
|
||||
@@ -17,6 +17,23 @@
|
||||
variable "folders" {
|
||||
description = "Folders data merged with factory data."
|
||||
type = map(object({
|
||||
asset_feeds = optional(map(object({
|
||||
billing_project = string
|
||||
content_type = optional(string)
|
||||
asset_types = optional(list(string))
|
||||
asset_names = optional(list(string))
|
||||
feed_output_config = object({
|
||||
pubsub_destination = object({
|
||||
topic = string
|
||||
})
|
||||
})
|
||||
condition = optional(object({
|
||||
expression = string
|
||||
title = optional(string)
|
||||
description = optional(string)
|
||||
location = optional(string)
|
||||
}))
|
||||
})), {})
|
||||
name = optional(string)
|
||||
parent = optional(string)
|
||||
deletion_protection = optional(bool)
|
||||
|
||||
@@ -17,6 +17,23 @@
|
||||
variable "projects" {
|
||||
description = "Projects data merged with factory data."
|
||||
type = map(object({
|
||||
asset_feeds = optional(map(object({
|
||||
billing_project = optional(string)
|
||||
content_type = optional(string)
|
||||
asset_types = optional(list(string))
|
||||
asset_names = optional(list(string))
|
||||
feed_output_config = object({
|
||||
pubsub_destination = object({
|
||||
topic = string
|
||||
})
|
||||
})
|
||||
condition = optional(object({
|
||||
expression = string
|
||||
title = optional(string)
|
||||
description = optional(string)
|
||||
location = optional(string)
|
||||
}))
|
||||
})), {})
|
||||
automation = optional(object({
|
||||
project = string
|
||||
bucket = optional(object({
|
||||
|
||||
@@ -28,6 +28,7 @@ variable "context" {
|
||||
notification_channels = optional(map(string), {})
|
||||
project_ids = optional(map(string), {})
|
||||
project_numbers = optional(map(string), {})
|
||||
pubsub_topics = optional(map(string), {})
|
||||
tag_values = optional(map(string), {})
|
||||
vpc_host_projects = optional(map(string), {})
|
||||
vpc_sc_perimeters = optional(map(string), {})
|
||||
|
||||
File diff suppressed because one or more lines are too long
46
modules/project/assets.tf
Normal file
46
modules/project/assets.tf
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright 2026 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.
|
||||
*/
|
||||
|
||||
resource "google_cloud_asset_project_feed" "default" {
|
||||
for_each = var.asset_feeds
|
||||
project = local.project.project_id
|
||||
billing_project = coalesce(each.value.billing_project, local.project.project_id)
|
||||
feed_id = each.key
|
||||
content_type = each.value.content_type
|
||||
|
||||
asset_types = each.value.asset_types
|
||||
asset_names = each.value.asset_names
|
||||
|
||||
feed_output_config {
|
||||
pubsub_destination {
|
||||
topic = lookup(
|
||||
local.ctx.pubsub_topics,
|
||||
each.value.feed_output_config.pubsub_destination.topic,
|
||||
each.value.feed_output_config.pubsub_destination.topic
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
dynamic "condition" {
|
||||
for_each = each.value.condition == null ? [] : [each.value.condition]
|
||||
content {
|
||||
expression = condition.value.expression
|
||||
title = condition.value.title
|
||||
description = condition.value.description
|
||||
location = condition.value.location
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,16 +19,38 @@
|
||||
locals {
|
||||
logging_sinks = {
|
||||
for k, v in var.logging_sinks :
|
||||
# rewrite destination and type when type="project"
|
||||
k => merge(v, v.type != "project" ? {} : {
|
||||
destination = "projects/${v.destination}"
|
||||
type = "logging"
|
||||
})
|
||||
# expand destination contexts
|
||||
k => merge(v,
|
||||
v.type != "bigquery" ? {} : {
|
||||
destination = lookup(
|
||||
local.ctx.bigquery_datasets, v.destination, v.destination
|
||||
)
|
||||
},
|
||||
v.type != "logging" ? {} : {
|
||||
destination = lookup(
|
||||
local.ctx.log_buckets, v.destination, v.destination
|
||||
)
|
||||
},
|
||||
v.type != "project" ? {} : {
|
||||
api = "logging"
|
||||
destination = "projects/${lookup(local.ctx.project_ids, v.destination, v.destination)}"
|
||||
},
|
||||
v.type != "pubsub" ? {} : {
|
||||
destination = lookup(
|
||||
local.ctx.pubsub_topics, v.destination, v.destination
|
||||
)
|
||||
},
|
||||
v.type != "storage" ? {} : {
|
||||
destination = lookup(
|
||||
local.ctx.storage_buckets, v.destination, v.destination
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
sink_bindings = {
|
||||
for type in ["bigquery", "logging", "project", "pubsub", "storage"] :
|
||||
type => {
|
||||
for name, sink in var.logging_sinks :
|
||||
for name, sink in local.logging_sinks :
|
||||
name => sink if sink.iam && sink.type == type
|
||||
}
|
||||
}
|
||||
@@ -66,7 +88,7 @@ resource "google_logging_project_sink" "sink" {
|
||||
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 = "${lookup(each.value, "api", each.value.type)}.googleapis.com/${each.value.destination}"
|
||||
filter = each.value.filter
|
||||
unique_writer_identity = each.value.unique_writer
|
||||
disabled = each.value.disabled
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
* Copyright 2026 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,6 +14,40 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
variable "asset_feeds" {
|
||||
description = "Cloud Asset Inventory feeds."
|
||||
type = map(object({
|
||||
billing_project = optional(string)
|
||||
content_type = optional(string)
|
||||
asset_types = optional(list(string))
|
||||
asset_names = optional(list(string))
|
||||
feed_output_config = object({
|
||||
pubsub_destination = object({
|
||||
topic = string
|
||||
})
|
||||
})
|
||||
condition = optional(object({
|
||||
expression = string
|
||||
title = optional(string)
|
||||
description = optional(string)
|
||||
location = optional(string)
|
||||
}))
|
||||
}))
|
||||
default = {}
|
||||
nullable = false
|
||||
validation {
|
||||
condition = alltrue([
|
||||
for k, v in var.asset_feeds :
|
||||
v.content_type == null || contains(
|
||||
["RESOURCE", "IAM_POLICY", "ORG_POLICY", "ACCESS_POLICY", "OS_INVENTORY", "RELATIONSHIP"],
|
||||
v.content_type
|
||||
)
|
||||
])
|
||||
error_message = "Content type must be one of RESOURCE, IAM_POLICY, ORG_POLICY, ACCESS_POLICY, OS_INVENTORY, RELATIONSHIP."
|
||||
}
|
||||
}
|
||||
|
||||
variable "auto_create_network" {
|
||||
description = "Whether to create the default network for the project."
|
||||
type = bool
|
||||
@@ -91,15 +125,18 @@ variable "contacts" {
|
||||
variable "context" {
|
||||
description = "Context-specific interpolations."
|
||||
type = object({
|
||||
bigquery_datasets = optional(map(string), {})
|
||||
condition_vars = optional(map(map(string)), {})
|
||||
custom_roles = optional(map(string), {})
|
||||
email_addresses = optional(map(string), {})
|
||||
folder_ids = optional(map(string), {})
|
||||
kms_keys = optional(map(string), {})
|
||||
iam_principals = optional(map(string), {})
|
||||
notification_channels = optional(map(string), {})
|
||||
kms_keys = optional(map(string), {})
|
||||
log_buckets = optional(map(string), {})
|
||||
notification_channels = optional(map(string), {})
|
||||
project_ids = optional(map(string), {})
|
||||
pubsub_topics = optional(map(string), {})
|
||||
storage_buckets = optional(map(string), {})
|
||||
tag_keys = optional(map(string), {})
|
||||
tag_values = optional(map(string), {})
|
||||
vpc_sc_perimeters = optional(map(string), {})
|
||||
|
||||
Reference in New Issue
Block a user