diff --git a/fast/stages/0-org-setup/README-GCD.md b/fast/stages/0-org-setup/README-GCD.md index e511a2838..0dfbc3e5e 100644 --- a/fast/stages/0-org-setup/README-GCD.md +++ b/fast/stages/0-org-setup/README-GCD.md @@ -1,6 +1,6 @@ # FAST Installation on Google Cloud Dedicated (GCD) -This document serves as an extension to the main **[FAST Organization Setup README](../README.md)**, detailing the specific configurations and steps required to deploy the Fabric FAST landing zone on **Google Cloud Dedicated (GCD)**. +This document serves as an extension to the main **[FAST Organization Setup README](./README.md)**, detailing the specific configurations and steps required to deploy the Fabric FAST landing zone on **Google Cloud Dedicated (GCD)**. It assumes familiarity with the standard FAST bootstrap flow but highlights the critical divergences required for the Google Cloud Dedicated (GCD) environment. @@ -31,7 +31,7 @@ The core stages are: ## 2. Prerequisites -In addition to the [standard FAST prerequisites](../README.md#prerequisites), ensure the following GCD-specific requirements are met. +In addition to the [standard FAST prerequisites](./README.md#prerequisites), ensure the following GCD-specific requirements are met. ### Identity Provider @@ -84,7 +84,7 @@ gcloud auth application-default login \ ## 3. Bootstrap: Manual Temporary Project -*This step replaces the standard [Default project](../README.md#default-project) creation flow.* +*This step replaces the standard [Default project](./README.md#default-project) creation flow.* GCD requires a manual bootstrap project because organization policy services are not automatically available at the organization root during the initial setup. @@ -113,7 +113,7 @@ GCD requires a manual bootstrap project because organization policy services are ## 4. Terraform Configuration Updates -*This section details specific modifications to the [Configure defaults](../README.md#configure-defaults) step.* +*This section details specific modifications to the [Configure defaults](./README.md#configure-defaults) step.* ### Provider Configuration @@ -134,7 +134,16 @@ provider "google-beta" { Update your `defaults.yaml` file to include a `universe` block within the `overrides` section. This configures the correct API domains and disables service identities that are not available in GCD. +Additionally, you must provide valid values for the following fields in the context section: +* `context.email_addresses.gcp-organization-admins`: used to set the [essential contact](https://docs.cloud.google.com/resource-manager/docs/manage-essential-contacts) for the core projects +* `context.iam_principals.gcp-organization-admins`: Used to grant administrative permissions to the administrators. + + **Note on Principals:** If you use a group for the admin principal, ensure your user identity is a member of that group. Otherwise, set this field to your own user identity (e.g., `principal://iam.googleapis.com/locations/global/workforcePools/...`) instead of a group. For further details, refer to the [Configure defaults](./README.md#configure-defaults) section in the standard README. + +Your `defaults.yaml should` contain sections that look like this: + ```yaml +# ... existing configuration ... projects: defaults: # customize prefix as per usual FAST instructions @@ -154,6 +163,15 @@ projects: - dns.googleapis.com - monitoring.googleapis.com - networksecurity.googleapis.com +context: + email_addresses: + gcp-organization-admins: gcp-organization-admins@example.com + iam_principals: + gcp-organization-admins: group:gcp-organization-admins@example.com + locations: + # Replace with values from the Configuration Reference table + primary: +# ... existing configuration ... ``` ### Switch to GCD Dataset @@ -259,4 +277,3 @@ Once the **Organization Setup** stage is fully deployed: ``` 2. **Proceed to Next Stages:** Continue with the subsequent FAST stages (VPC-SC, Security, Networking, Project Factory). The universe configuration established here is automatically propagated to these stages via the FAST cross-stage output mechanism. - diff --git a/fast/stages/0-org-setup/datasets/classic-gcd/projects/core/billing-0.yaml b/fast/stages/0-org-setup/datasets/classic-gcd/projects/core/billing-0.yaml index 29070bbc2..65db76843 100644 --- a/fast/stages/0-org-setup/datasets/classic-gcd/projects/core/billing-0.yaml +++ b/fast/stages/0-org-setup/datasets/classic-gcd/projects/core/billing-0.yaml @@ -1,4 +1,4 @@ -# 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. @@ -22,7 +22,7 @@ iam_by_principals: - roles/owner services: - bigquery.googleapis.com - - bigquerydatatransfer.googleapis.com + # - bigquerydatatransfer.googleapis.com - storage.googleapis.com datasets: billing_export: diff --git a/fast/stages/0-org-setup/datasets/classic/observability/iac-0/impersonation.yaml b/fast/stages/0-org-setup/datasets/classic/observability/iac-0/impersonation.yaml new file mode 100644 index 000000000..632be9ef2 --- /dev/null +++ b/fast/stages/0-org-setup/datasets/classic/observability/iac-0/impersonation.yaml @@ -0,0 +1,63 @@ +# 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. + +# yaml-language-server: $schema=../../../../schemas/observability.schema.json + +notification_channels: + email-security: + type: email + display_name: Security Team Email + labels: + email_address: $email_addresses:gcp-organization-admins + enabled: true + +logging_metrics: + sa-impersonation: + filter: | + protoPayload.serviceName="iamcredentials.googleapis.com" + (protoPayload.methodName="GenerateAccessToken" OR protoPayload.methodName="GenerateIdToken") + label_extractors: + email_id: EXTRACT(resource.labels.email_id) + metric_descriptor: + metric_kind: DELTA + value_type: INT64 + unit: "1" + display_name: Service Account Impersonation + labels: + - key: email_id + value_type: STRING + +alerts: + sa-impersonation-alert: + display_name: Service Account Impersonation Alert + combiner: OR + conditions: + - display_name: Impersonation Detected + condition_threshold: + filter: | + metric.type="logging.googleapis.com/user/sa-impersonation" AND + resource.type="global" + comparison: COMPARISON_GT + threshold_value: 0 + duration: 60s + trigger: + count: 1 + aggregations: + alignment_period: 60s + per_series_aligner: ALIGN_COUNT + cross_series_reducer: REDUCE_SUM + group_by_fields: ["metric.label.email_id"] + notification_channels: + - email-security + enabled: true diff --git a/fast/stages/0-org-setup/datasets/classic/projects/core/iac-0.yaml b/fast/stages/0-org-setup/datasets/classic/projects/core/iac-0.yaml index 0b234f28f..cb1ba257e 100644 --- a/fast/stages/0-org-setup/datasets/classic/projects/core/iac-0.yaml +++ b/fast/stages/0-org-setup/datasets/classic/projects/core/iac-0.yaml @@ -1,4 +1,4 @@ -# 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. @@ -83,6 +83,8 @@ org_policies: - https://token.actions.githubusercontent.com - https://gitlab.com - https://app.terraform.io +factories_config: + observability: datasets/classic/observability/iac-0 data_access_logs: storage.googleapis.com: DATA_READ: {} @@ -90,6 +92,10 @@ data_access_logs: sts.googleapis.com: DATA_READ: {} DATA_WRITE: {} + # required to capture service account impersonation events + iam.googleapis.com: + DATA_READ: {} + DATA_WRITE: {} buckets: # Terraform state bucket for this stage iac-org-state: diff --git a/modules/looker-core/README.md b/modules/looker-core/README.md index 5a3c48ee2..8e959d3a8 100644 --- a/modules/looker-core/README.md +++ b/modules/looker-core/README.md @@ -19,6 +19,7 @@ is no terraform support for these resources. - [Examples](#examples) - [Simple example](#simple-example) - [Looker Core private instance with PSA](#looker-core-private-instance-with-psa) + - [Looker Core with PSC](#looker-core-with-psc) - [Looker Core full example](#looker-core-full-example) - [Variables](#variables) - [Outputs](#outputs) @@ -90,6 +91,29 @@ module "looker" { # tftest modules=3 resources=17 inventory=psa.yaml ``` + +### Looker Core with PSC + +```hcl +module "looker" { + source = "./fabric/modules/looker-core" + project_id = var.project_id + region = var.region + name = "looker-psc" + network_config = { + psc_config = { + allowed_vpcs = ["projects/test-project/global/networks/test"] + } + } + oauth_config = { + client_id = "xxxxxxxxx" + client_secret = "xxxxxxxx" + } + platform_edition = "LOOKER_CORE_ENTERPRISE_ANNUAL" +} +# tftest inventory=psc.yaml +``` + ### Looker Core full example ```hcl @@ -160,23 +184,22 @@ module "looker" { } # tftest modules=4 resources=23 inventory=full.yaml ``` - ## Variables | name | description | type | required | default | |---|---|:---:|:---:|:---:| | [name](variables.tf#L91) | Name of the looker core instance. | string | ✓ | | -| [network_config](variables.tf#L96) | Network configuration for cluster and instance. Only one between psa_config and psc_config can be used. | object({…}) | ✓ | | -| [oauth_config](variables.tf#L114) | Looker Core Oauth config. Either client ID and secret (existing oauth client) or support email (temporary internal oauth client setup) must be specified. | object({…}) | ✓ | | -| [project_id](variables.tf#L147) | The ID of the project where this instances will be created. | string | ✓ | | -| [region](variables.tf#L152) | Region for the Looker core instance. | string | ✓ | | +| [network_config](variables.tf#L96) | Network configuration for cluster and instance. Only one between psa_config, psc_config and public can be used. | object({…}) | ✓ | | +| [oauth_config](variables.tf#L121) | Looker Core Oauth config. Either client ID and secret (existing oauth client) or support email (temporary internal oauth client setup) must be specified. | object({…}) | ✓ | | +| [project_id](variables.tf#L154) | The ID of the project where this instances will be created. | string | ✓ | | +| [region](variables.tf#L159) | Region for the Looker core instance. | string | ✓ | | | [admin_settings](variables.tf#L17) | Looker Core admins settings. | object({…}) | | null | | [custom_domain](variables.tf#L26) | Looker core instance custom domain. | string | | null | | [encryption_config](variables.tf#L32) | Set encryption configuration. KMS name format: 'projects/[PROJECT]/locations/[REGION]/keyRings/[RING]/cryptoKeys/[KEY_NAME]'. | object({…}) | | null | | [maintenance_config](variables.tf#L41) | Set maintenance window configuration and maintenance deny period (up to 90 days). Date format: 'yyyy-mm-dd'. | object({…}) | | {} | -| [platform_edition](variables.tf#L127) | Platform editions for a Looker instance. Each edition maps to a set of instance features, like its size. | string | | "LOOKER_CORE_TRIAL" | -| [prefix](variables.tf#L137) | Optional prefix used to generate instance names. | string | | null | +| [platform_edition](variables.tf#L134) | Platform editions for a Looker instance. Each edition maps to a set of instance features, like its size. | string | | "LOOKER_CORE_TRIAL" | +| [prefix](variables.tf#L144) | Optional prefix used to generate instance names. | string | | null | ## Outputs diff --git a/modules/looker-core/main.tf b/modules/looker-core/main.tf index 8278fec6b..4cf8f338b 100644 --- a/modules/looker-core/main.tf +++ b/modules/looker-core/main.tf @@ -29,6 +29,7 @@ resource "google_looker_instance" "looker" { platform_edition = var.platform_edition private_ip_enabled = try(var.network_config.psa_config.enable_private_ip, null) public_ip_enabled = coalesce(var.network_config.public, false) || try(var.network_config.psa_config.enable_public_ip, false) + psc_enabled = var.network_config.psc_config != null region = var.region reserved_range = try(var.network_config.psa_config.allocated_ip_range, null) @@ -37,6 +38,13 @@ resource "google_looker_instance" "looker" { client_secret = local.oauth_client_secret } + dynamic "psc_config" { + for_each = var.network_config.psc_config != null ? [""] : [] + content { + allowed_vpcs = var.network_config.psc_config.allowed_vpcs + } + } + dynamic "admin_settings" { for_each = var.admin_settings != null ? [""] : [] content { diff --git a/modules/looker-core/variables.tf b/modules/looker-core/variables.tf index bc28c71d1..e604c0e8c 100644 --- a/modules/looker-core/variables.tf +++ b/modules/looker-core/variables.tf @@ -94,7 +94,7 @@ variable "name" { } variable "network_config" { - description = "Network configuration for cluster and instance. Only one between psa_config and psc_config can be used." + description = "Network configuration for cluster and instance. Only one between psa_config, psc_config and public can be used." type = object({ psa_config = optional(object({ network = string @@ -102,12 +102,19 @@ variable "network_config" { enable_public_ip = optional(bool, false) enable_private_ip = optional(bool, true) })) + psc_config = optional(object({ + allowed_vpcs = optional(list(string), []) + })) public = optional(bool, false) }) nullable = false validation { - condition = (coalesce(var.network_config.public, false)) == (var.network_config.psa_config == null) - error_message = "Please specify either psa_config or public to true." + condition = ( + (coalesce(var.network_config.public, false) ? 1 : 0) + + (var.network_config.psa_config != null ? 1 : 0) + + (var.network_config.psc_config != null ? 1 : 0) + ) == 1 + error_message = "Please specify exactly one of psa_config, psc_config or public." } } diff --git a/modules/project/alerts.tf b/modules/project/alerts.tf index ef2139c06..a28a67dc4 100644 --- a/modules/project/alerts.tf +++ b/modules/project/alerts.tf @@ -135,7 +135,7 @@ resource "google_monitoring_alert_policy" "alerts" { # first try to get a channel created by this module google_monitoring_notification_channel.channels[x].name, # otherwise check the context - var.context.notification_channels[x], + local.ctx.notification_channels[x], # if nothing else, use the provided channel as is x ) diff --git a/modules/project/logging-metrics.tf b/modules/project/logging-metrics.tf index 59dc7088e..42ba6f29d 100644 --- a/modules/project/logging-metrics.tf +++ b/modules/project/logging-metrics.tf @@ -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. @@ -71,7 +71,7 @@ resource "google_logging_metric" "metrics" { disabled = each.value.disabled bucket_name = try( # first try to check the context - var.context.log_buckets[each.value.bucket_name], + local.ctx.log_buckets[each.value.bucket_name], # if nothing else, use the provided channel as is each.value.bucket_name ) diff --git a/modules/project/notification-channels.tf b/modules/project/notification-channels.tf index 45d1678c6..1827b07f9 100644 --- a/modules/project/notification-channels.tf +++ b/modules/project/notification-channels.tf @@ -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. @@ -45,8 +45,16 @@ resource "google_monitoring_notification_channel" "channels" { description = each.value.description display_name = each.value.display_name enabled = each.value.enabled - labels = each.value.labels - user_labels = each.value.user_labels + labels = each.value.labels == null ? null : { + for k, v in each.value.labels : + # allow interpolation of email addresses and pubsub topics + k => try( + local.ctx.email_addresses[v], + local.ctx.pubsub_topics[v], + v + ) + } + user_labels = each.value.user_labels dynamic "sensitive_labels" { for_each = each.value.sensitive_labels[*] content { diff --git a/tests/fast/stages/s0_org_setup/simple.yaml b/tests/fast/stages/s0_org_setup/simple.yaml index c9c855a3a..16395beb7 100644 --- a/tests/fast/stages/s0_org_setup/simple.yaml +++ b/tests/fast/stages/s0_org_setup/simple.yaml @@ -1,4 +1,4 @@ -# 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. @@ -34,6 +34,7 @@ values: content_disposition: null content_encoding: null content_language: null + contexts: [] customer_encryption: [] deletion_policy: null detect_md5hash: null @@ -65,6 +66,7 @@ values: content_disposition: null content_encoding: null content_language: null + contexts: [] customer_encryption: [] deletion_policy: null detect_md5hash: null @@ -97,6 +99,7 @@ values: content_disposition: null content_encoding: null content_language: null + contexts: [] customer_encryption: [] deletion_policy: null detect_md5hash: null @@ -129,6 +132,7 @@ values: content_disposition: null content_encoding: null content_language: null + contexts: [] customer_encryption: [] deletion_policy: null detect_md5hash: null @@ -161,6 +165,7 @@ values: content_disposition: null content_encoding: null content_language: null + contexts: [] customer_encryption: [] deletion_policy: null detect_md5hash: null @@ -193,6 +198,7 @@ values: content_disposition: null content_encoding: null content_language: null + contexts: [] customer_encryption: [] deletion_policy: null detect_md5hash: null @@ -211,6 +217,7 @@ values: content_disposition: null content_encoding: null content_language: null + contexts: [] customer_encryption: [] deletion_policy: null detect_md5hash: null @@ -228,6 +235,7 @@ values: content_disposition: null content_encoding: null content_language: null + contexts: [] customer_encryption: [] deletion_policy: null detect_md5hash: null @@ -245,6 +253,7 @@ values: content_disposition: null content_encoding: null content_language: null + contexts: [] customer_encryption: [] deletion_policy: null detect_md5hash: null @@ -357,6 +366,7 @@ values: content_disposition: null content_encoding: null content_language: null + contexts: [] customer_encryption: [] deletion_policy: null detect_md5hash: null @@ -1112,6 +1122,14 @@ values: condition: [] project: ft0-prod-billing-exp-0 role: roles/viewer + module.factory.module.projects-iam["iac-0"].google_project_iam_audit_config.default["iam.googleapis.com"]: + audit_log_config: + - exempted_members: [] + log_type: DATA_READ + - exempted_members: [] + log_type: DATA_WRITE + project: ft0-prod-iac-core-0 + service: iam.googleapis.com module.factory.module.projects-iam["iac-0"].google_project_iam_audit_config.default["storage.googleapis.com"]: audit_log_config: - exempted_members: [] @@ -1241,6 +1259,82 @@ values: module.factory.module.projects["iac-0"].data.google_storage_project_service_account.gcs_sa[0]: project: ft0-prod-iac-core-0 user_project: null + module.factory.module.projects["iac-0"].google_logging_metric.metrics["sa-impersonation"]: + bucket_name: null + bucket_options: [] + description: null + disabled: null + filter: 'protoPayload.serviceName="iamcredentials.googleapis.com" + + (protoPayload.methodName="GenerateAccessToken" OR protoPayload.methodName="GenerateIdToken") + + ' + label_extractors: + email_id: EXTRACT(resource.labels.email_id) + metric_descriptor: + - display_name: Service Account Impersonation + labels: + - description: '' + key: email_id + value_type: STRING + metric_kind: DELTA + unit: '1' + value_type: INT64 + name: sa-impersonation + project: ft0-prod-iac-core-0 + timeouts: null + value_extractor: null + module.factory.module.projects["iac-0"].google_monitoring_alert_policy.alerts["sa-impersonation-alert"]: + alert_strategy: [] + combiner: OR + conditions: + - condition_absent: [] + condition_matched_log: [] + condition_monitoring_query_language: [] + condition_prometheus_query_language: [] + condition_sql: [] + condition_threshold: + - aggregations: + - alignment_period: 60s + cross_series_reducer: REDUCE_SUM + group_by_fields: + - metric.label.email_id + per_series_aligner: ALIGN_COUNT + comparison: COMPARISON_GT + denominator_aggregations: [] + denominator_filter: null + duration: 60s + evaluation_missing_data: null + filter: 'metric.type="logging.googleapis.com/user/sa-impersonation" AND + + resource.type="global" + + ' + forecast_options: [] + threshold_value: 0 + trigger: + - count: 1 + percent: null + display_name: Impersonation Detected + display_name: Service Account Impersonation Alert + documentation: [] + enabled: true + project: ft0-prod-iac-core-0 + severity: null + timeouts: null + user_labels: null + module.factory.module.projects["iac-0"].google_monitoring_notification_channel.channels["email-security"]: + description: null + display_name: Security Team Email + enabled: true + force_delete: false + labels: + email_address: $email_addresses:gcp-organization-admins + project: ft0-prod-iac-core-0 + sensitive_labels: [] + timeouts: null + type: email + user_labels: null module.factory.module.projects["iac-0"].google_org_policy_policy.default["iam.workloadIdentityPoolProviders"]: dry_run_spec: [] name: projects/ft0-prod-iac-core-0/policies/iam.workloadIdentityPoolProviders @@ -2862,6 +2956,7 @@ values: stage: GA title: Custom role tagViewer module.organization[0].google_tags_tag_key.default["context"]: + allowed_values_regex: null description: Organization-level contexts. parent: organizations/1234567890 purpose: null @@ -2869,6 +2964,7 @@ values: short_name: context timeouts: null module.organization[0].google_tags_tag_key.default["environment"]: + allowed_values_regex: null description: Organization-level environments. parent: organizations/1234567890 purpose: null @@ -2876,6 +2972,7 @@ values: short_name: environment timeouts: null module.organization[0].google_tags_tag_key.default["org-policies"]: + allowed_values_regex: null description: Organization policy condition tags. parent: organizations/1234567890 purpose: null @@ -2910,7 +3007,6 @@ values: input: null output: null triggers_replace: null - counts: google_bigquery_dataset: 1 google_bigquery_default_service_account: 2 @@ -2918,17 +3014,20 @@ counts: google_essential_contacts_contact: 1 google_folder: 10 google_folder_iam_binding: 44 + google_logging_metric: 1 google_logging_organization_settings: 1 google_logging_organization_sink: 3 google_logging_project_bucket_config: 3 google_logging_project_settings: 2 + google_monitoring_alert_policy: 1 + google_monitoring_notification_channel: 1 google_org_policy_custom_constraint: 1 google_org_policy_policy: 37 google_organization_iam_audit_config: 1 google_organization_iam_binding: 37 google_organization_iam_custom_role: 9 google_project: 3 - google_project_iam_audit_config: 2 + google_project_iam_audit_config: 3 google_project_iam_binding: 17 google_project_iam_member: 15 google_project_service: 33 @@ -2948,5 +3047,5 @@ counts: google_tags_tag_value_iam_binding: 4 local_file: 9 modules: 50 - resources: 324 + resources: 328 terraform_data: 4 diff --git a/tests/modules/looker_core/examples/psc.yaml b/tests/modules/looker_core/examples/psc.yaml new file mode 100644 index 000000000..a82e96b59 --- /dev/null +++ b/tests/modules/looker_core/examples/psc.yaml @@ -0,0 +1,51 @@ +# 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. + +values: + module.looker.google_looker_instance.looker: + admin_settings: [] + consumer_network: null + controlled_egress_config: [] + controlled_egress_enabled: null + custom_domain: [] + deletion_policy: DEFAULT + deny_maintenance_period: [] + fips_enabled: null + gemini_enabled: null + maintenance_window: [] + name: looker-psc + oauth_config: + - client_id: xxxxxxxxx + client_secret: xxxxxxxx + periodic_export_config: [] + platform_edition: LOOKER_CORE_ENTERPRISE_ANNUAL + private_ip_enabled: false + project: project-id + psc_config: + - allowed_vpcs: + - projects/test-project/global/networks/test + service_attachments: [] + psc_enabled: true + public_ip_enabled: false + region: europe-west8 + reserved_range: null + timeouts: null + user_metadata: [] + +counts: + google_looker_instance: 1 + modules: 1 + resources: 1 + +outputs: {} diff --git a/tests/modules/project/context.tfvars b/tests/modules/project/context.tfvars index 476f74134..db7277832 100644 --- a/tests/modules/project/context.tfvars +++ b/tests/modules/project/context.tfvars @@ -31,6 +31,12 @@ context = { tag_values = { "test/one" = "tagValues/1234567890" } + log_buckets = { + audit = "logging.googleapis.com/projects/my-project/locations/global/buckets/audit-bucket" + } + notification_channels = { + email = "projects/my-project/notificationChannels/12345" + } vpc_sc_perimeters = { default = "accessPolicies/888933661165/servicePerimeters/default" } @@ -38,6 +44,41 @@ context = { test = "projects/test-prod-audit-logs-0/topics/audit-logs" } } +alerts = { + test-alert = { + combiner = "OR" + display_name = "Test Alert" + conditions = [{ + display_name = "test-condition" + condition_threshold = { + comparison = "COMPARISON_GT" + duration = "60s" + filter = "resource.type=\"gce_instance\" AND metric.type=\"compute.googleapis.com/instance/cpu/utilization\"" + } + }] + notification_channels = ["$notification_channels:email"] + } +} +logging_metrics = { + test-metric = { + filter = "resource.type=\"gce_instance\"" + bucket_name = "$log_buckets:audit" + } +} +notification_channels = { + new-email = { + type = "email" + labels = { + email_address = "$email_addresses:default" + } + } + new-pubsub = { + type = "pubsub" + labels = { + topic = "$pubsub_topics:test" + } + } +} asset_feeds = { test = { billing_project = "test-project" diff --git a/tests/modules/project/context.yaml b/tests/modules/project/context.yaml index dd888c331..b45ea1424 100644 --- a/tests/modules/project/context.yaml +++ b/tests/modules/project/context.yaml @@ -44,6 +44,17 @@ values: condition: [] crypto_key_id: projects/kms-central-prj/locations/europe-west1/keyRings/my-keyring/cryptoKeys/ew1-compute role: roles/cloudkms.cryptoKeyEncrypterDecrypter + google_logging_metric.metrics["test-metric"]: + bucket_name: logging.googleapis.com/projects/my-project/locations/global/buckets/audit-bucket + bucket_options: [] + description: null + disabled: null + filter: resource.type="gce_instance" + label_extractors: null + name: test-metric + project: my-project + timeouts: null + value_extractor: null google_logging_project_sink.sink["test-pubsub"]: custom_writer_identity: null description: test-pubsub (Terraform-managed). @@ -54,6 +65,60 @@ values: name: test-pubsub project: my-project unique_writer_identity: true + google_monitoring_alert_policy.alerts["test-alert"]: + alert_strategy: [] + combiner: OR + conditions: + - condition_absent: [] + condition_matched_log: [] + condition_monitoring_query_language: [] + condition_prometheus_query_language: [] + condition_sql: [] + condition_threshold: + - aggregations: [] + comparison: COMPARISON_GT + denominator_aggregations: [] + denominator_filter: null + duration: 60s + evaluation_missing_data: null + filter: resource.type="gce_instance" AND metric.type="compute.googleapis.com/instance/cpu/utilization" + forecast_options: [] + threshold_value: null + trigger: [] + display_name: test-condition + display_name: Test Alert + documentation: [] + enabled: true + notification_channels: + - projects/my-project/notificationChannels/12345 + project: my-project + severity: null + timeouts: null + user_labels: null + google_monitoring_notification_channel.channels["new-email"]: + description: null + display_name: null + enabled: true + force_delete: false + labels: + email_address: foo@example.com + project: my-project + sensitive_labels: [] + timeouts: null + type: email + user_labels: null + google_monitoring_notification_channel.channels["new-pubsub"]: + description: null + display_name: null + enabled: true + force_delete: false + labels: + topic: projects/test-prod-audit-logs-0/topics/audit-logs + project: my-project + sensitive_labels: [] + timeouts: null + type: pubsub + user_labels: null google_privileged_access_manager_entitlement.default["net-admins"]: additional_notification_targets: [] approval_workflow: @@ -253,7 +318,10 @@ counts: google_compute_shared_vpc_service_project: 1 google_essential_contacts_contact: 1 google_kms_crypto_key_iam_member: 1 + google_logging_metric: 1 google_logging_project_sink: 1 + google_monitoring_alert_policy: 1 + google_monitoring_notification_channel: 2 google_privileged_access_manager_entitlement: 1 google_project: 1 google_project_iam_audit_config: 1 @@ -267,4 +335,4 @@ counts: google_tags_tag_value_iam_binding: 2 google_tags_tag_value_iam_member: 1 modules: 0 - resources: 32 + resources: 36 diff --git a/tools/check_links.py b/tools/check_links.py index a6ce48a30..d9624a5b3 100755 --- a/tools/check_links.py +++ b/tools/check_links.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# Copyright 2023 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. @@ -13,6 +13,15 @@ # 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. +# +# /// script +# requires-python = ">=3.11" +# dependencies = [ +# "click", +# "marko", +# "requests", +# ] +# /// '''Recursively check link destination validity in Markdown files. This tool recursively checks that local links in Markdown files point to valid diff --git a/tools/duplicate-diff.py b/tools/duplicate-diff.py index 479b75540..d014d9f21 100755 --- a/tools/duplicate-diff.py +++ b/tools/duplicate-diff.py @@ -60,6 +60,10 @@ duplicates = [ "fast/stages/2-security/schemas/folder.schema.json", "modules/project-factory/schemas/folder.schema.json", ], + [ + "fast/stages/0-org-setup/schemas/observability.schema.json", + "modules/project/schemas/observability.schema.json", + ], [ "fast/stages/1-vpcsc/schemas/ingress-policy.schema.json", "modules/vpc-sc/schemas/ingress-policy.schema.json",