From dd213ea9a9df1f4c1d8b5bd64bfc79d7284262cd Mon Sep 17 00:00:00 2001 From: Luca Prete Date: Tue, 23 Apr 2024 15:19:38 +0200 Subject: [PATCH 01/31] Fix permissions for branch network dev - read sa (#2233) Co-authored-by: Luca Prete --- fast/stages/1-resman/branch-networking.tf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fast/stages/1-resman/branch-networking.tf b/fast/stages/1-resman/branch-networking.tf index e57a874ff6..e886234bdb 100644 --- a/fast/stages/1-resman/branch-networking.tf +++ b/fast/stages/1-resman/branch-networking.tf @@ -101,10 +101,10 @@ module "branch-network-dev-folder" { ) # read-only (plan) automation service accounts "roles/compute.networkViewer" = concat( - local.branch_optional_r_sa_lists.dp-prod, - local.branch_optional_r_sa_lists.gke-prod, + local.branch_optional_r_sa_lists.dp-dev, + local.branch_optional_r_sa_lists.gke-dev, local.branch_optional_r_sa_lists.gcve-dev, - local.branch_optional_r_sa_lists.pf-prod, + local.branch_optional_r_sa_lists.pf-dev, ) (local.custom_roles.gcve_network_admin) = local.branch_optional_sa_lists.gcve-dev } From d9019926076cbaec824d8aeee47824d073d2f26a Mon Sep 17 00:00:00 2001 From: luigi-bitonti <93377317+luigi-bitonti@users.noreply.github.com> Date: Tue, 23 Apr 2024 19:20:38 +0200 Subject: [PATCH 02/31] Added build env vars in cloud function v1 (#2234) --- modules/cloud-function-v1/README.md | 39 +++++++++++++------------- modules/cloud-function-v1/main.tf | 5 ++-- modules/cloud-function-v1/variables.tf | 6 ++++ 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/modules/cloud-function-v1/README.md b/modules/cloud-function-v1/README.md index 2d52842bb8..7099946e2d 100644 --- a/modules/cloud-function-v1/README.md +++ b/modules/cloud-function-v1/README.md @@ -264,26 +264,27 @@ module "cf-http" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| | [bucket_name](variables.tf#L26) | Name of the bucket that will be used for the function code. It will be created with prefix prepended if bucket_config is not null. | string | ✓ | | -| [bundle_config](variables.tf#L38) | Cloud function source folder and generated zip bundle paths. Output path defaults to '/tmp/bundle.zip' if null. | object({…}) | ✓ | | -| [name](variables.tf#L103) | Name used for cloud function and associated resources. | string | ✓ | | -| [project_id](variables.tf#L118) | Project id used for all resources. | string | ✓ | | -| [region](variables.tf#L123) | Region used for all resources. | string | ✓ | | +| [bundle_config](variables.tf#L44) | Cloud function source folder and generated zip bundle paths. Output path defaults to '/tmp/bundle.zip' if null. | object({…}) | ✓ | | +| [name](variables.tf#L109) | Name used for cloud function and associated resources. | string | ✓ | | +| [project_id](variables.tf#L124) | Project id used for all resources. | string | ✓ | | +| [region](variables.tf#L129) | Region used for all resources. | string | ✓ | | | [bucket_config](variables.tf#L17) | Enable and configure auto-created bucket. Set fields to null to use defaults. | object({…}) | | null | -| [build_worker_pool](variables.tf#L32) | Build worker pool, in projects//locations//workerPools/ format. | string | | null | -| [description](variables.tf#L47) | Optional description. | string | | "Terraform managed." | -| [environment_variables](variables.tf#L53) | Cloud function environment variables. | map(string) | | {} | -| [function_config](variables.tf#L59) | Cloud function configuration. Defaults to using main as entrypoint, 1 instance with 256MiB of memory, and 180 second timeout. | object({…}) | | {…} | -| [https_security_level](variables.tf#L79) | The security level for the function: Allowed values are SECURE_ALWAYS, SECURE_OPTIONAL. | string | | null | -| [iam](variables.tf#L85) | IAM bindings for topic in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | -| [ingress_settings](variables.tf#L91) | Control traffic that reaches the cloud function. Allowed values are ALLOW_ALL, ALLOW_INTERNAL_AND_GCLB and ALLOW_INTERNAL_ONLY . | string | | null | -| [labels](variables.tf#L97) | Resource labels. | map(string) | | {} | -| [prefix](variables.tf#L108) | Optional prefix used for resource names. | string | | null | -| [secrets](variables.tf#L128) | Secret Manager secrets. Key is the variable name or mountpoint, volume versions are in version:path format. | map(object({…})) | | {} | -| [service_account](variables.tf#L140) | Service account email. Unused if service account is auto-created. | string | | null | -| [service_account_create](variables.tf#L146) | Auto-create service account. | bool | | false | -| [trigger_config](variables.tf#L152) | Function trigger configuration. Leave null for HTTP trigger. | object({…}) | | null | -| [vpc_connector](variables.tf#L162) | VPC connector configuration. Set create to 'true' if a new connector needs to be created. | object({…}) | | null | -| [vpc_connector_config](variables.tf#L172) | VPC connector network configuration. Must be provided if new VPC connector is being created. | object({…}) | | null | +| [build_environment_variables](variables.tf#L32) | A set of key/value environment variable pairs available during build time. | map(string) | | {} | +| [build_worker_pool](variables.tf#L38) | Build worker pool, in projects//locations//workerPools/ format. | string | | null | +| [description](variables.tf#L53) | Optional description. | string | | "Terraform managed." | +| [environment_variables](variables.tf#L59) | Cloud function environment variables. | map(string) | | {} | +| [function_config](variables.tf#L65) | Cloud function configuration. Defaults to using main as entrypoint, 1 instance with 256MiB of memory, and 180 second timeout. | object({…}) | | {…} | +| [https_security_level](variables.tf#L85) | The security level for the function: Allowed values are SECURE_ALWAYS, SECURE_OPTIONAL. | string | | null | +| [iam](variables.tf#L91) | IAM bindings for topic in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | +| [ingress_settings](variables.tf#L97) | Control traffic that reaches the cloud function. Allowed values are ALLOW_ALL, ALLOW_INTERNAL_AND_GCLB and ALLOW_INTERNAL_ONLY . | string | | null | +| [labels](variables.tf#L103) | Resource labels. | map(string) | | {} | +| [prefix](variables.tf#L114) | Optional prefix used for resource names. | string | | null | +| [secrets](variables.tf#L134) | Secret Manager secrets. Key is the variable name or mountpoint, volume versions are in version:path format. | map(object({…})) | | {} | +| [service_account](variables.tf#L146) | Service account email. Unused if service account is auto-created. | string | | null | +| [service_account_create](variables.tf#L152) | Auto-create service account. | bool | | false | +| [trigger_config](variables.tf#L158) | Function trigger configuration. Leave null for HTTP trigger. | object({…}) | | null | +| [vpc_connector](variables.tf#L168) | VPC connector configuration. Set create to 'true' if a new connector needs to be created. | object({…}) | | null | +| [vpc_connector_config](variables.tf#L178) | VPC connector network configuration. Must be provided if new VPC connector is being created. | object({…}) | | null | ## Outputs diff --git a/modules/cloud-function-v1/main.tf b/modules/cloud-function-v1/main.tf index 8a9af6df6c..b57d8431cb 100644 --- a/modules/cloud-function-v1/main.tf +++ b/modules/cloud-function-v1/main.tf @@ -68,8 +68,9 @@ resource "google_cloudfunctions_function" "function" { trigger_http = var.trigger_config == null ? true : null https_trigger_security_level = var.https_security_level == null ? "SECURE_ALWAYS" : var.https_security_level - ingress_settings = var.ingress_settings - build_worker_pool = var.build_worker_pool + ingress_settings = var.ingress_settings + build_worker_pool = var.build_worker_pool + build_environment_variables = var.build_environment_variables vpc_connector = local.vpc_connector vpc_connector_egress_settings = try( diff --git a/modules/cloud-function-v1/variables.tf b/modules/cloud-function-v1/variables.tf index 567a8c7a7d..73964d0b18 100644 --- a/modules/cloud-function-v1/variables.tf +++ b/modules/cloud-function-v1/variables.tf @@ -29,6 +29,12 @@ variable "bucket_name" { nullable = false } +variable "build_environment_variables" { + description = "A set of key/value environment variable pairs available during build time." + type = map(string) + default = {} +} + variable "build_worker_pool" { description = "Build worker pool, in projects//locations//workerPools/ format." type = string From 99129d54a37da4c2d977b7db705de5024d530944 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 25 Apr 2024 09:31:51 +0300 Subject: [PATCH 03/31] Update FAST logging (#2235) * Update FAST logging * Fix readme * Fix tests --- fast/stages/0-bootstrap/README.md | 20 +++++++++------- fast/stages/0-bootstrap/automation.tf | 18 ++++++++++++++ fast/stages/0-bootstrap/variables.tf | 24 ++++++++++++++++--- tests/fast/stages/s0_bootstrap/checklist.yaml | 10 ++++---- tests/fast/stages/s0_bootstrap/simple.yaml | 10 ++++---- 5 files changed, 60 insertions(+), 22 deletions(-) diff --git a/fast/stages/0-bootstrap/README.md b/fast/stages/0-bootstrap/README.md index 83031f5d2a..ee5c7f52e7 100644 --- a/fast/stages/0-bootstrap/README.md +++ b/fast/stages/0-bootstrap/README.md @@ -138,7 +138,9 @@ Because of limitations of API availability, manual steps have to be followed to ### Organization-level logging -We create organization-level log sinks early in the bootstrap process to ensure a proper audit trail is in place from the very beginning. By default, we provide log filters to capture [Cloud Audit Logs](https://cloud.google.com/logging/docs/audit), [VPC Service Controls violations](https://cloud.google.com/vpc-service-controls/docs/troubleshooting#vpc-sc-errors) and [Workspace Logs](https://cloud.google.com/logging/docs/audit/configure-gsuite-audit-logs) into logging buckets in the top-level audit logging project. +We create organization-level log sinks early in the bootstrap process to ensure a proper audit trail is in place from the very beginning. By default, we provide log filters to capture [Cloud Audit Logs](https://cloud.google.com/logging/docs/audit), [VPC Service Controls violations](https://cloud.google.com/vpc-service-controls/docs/troubleshooting#vpc-sc-errors) and [Workspace Logs](https://cloud.google.com/logging/docs/audit/configure-gsuite-audit-logs) into logging buckets in the top-level audit logging project. + +An organization-level sink captures IAM data access logs, including authentication and impersonation events for service accounts. To manage logging costs, the default configuration enables IAM data access logging only within the automation project (where sensitive service accounts reside). For enhanced security across the entire organization, consider enabling these logs at the organization level. The [Customizations](#log-sinks-and-log-destinations) section explains how to change the logs captured and their destination. @@ -626,8 +628,8 @@ The `fast_features` variable consists of 4 toggles: | name | description | type | required | default | producer | |---|---|:---:|:---:|:---:|:---:| | [billing_account](variables.tf#L17) | Billing account id. If billing account is not part of the same org set `is_org_level` to `false`. To disable handling of billing IAM roles set `no_iam` to `true`. | object({…}) | ✓ | | | -| [organization](variables.tf#L223) | Organization details. | object({…}) | ✓ | | | -| [prefix](variables.tf#L238) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | | +| [organization](variables.tf#L241) | Organization details. | object({…}) | ✓ | | | +| [prefix](variables.tf#L256) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | | | [bootstrap_user](variables.tf#L27) | Email of the nominal user running this stage for the first time. | string | | null | | | [cicd_repositories](variables.tf#L33) | CI/CD repository configuration. Identity providers reference keys in the `federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…}) | | null | | | [custom_roles](variables.tf#L79) | Map of role names => list of permissions to additionally create at the organization level. | map(list(string)) | | {} | | @@ -639,12 +641,12 @@ The `fast_features` variable consists of 4 toggles: | [iam_bindings_additive](variables.tf#L141) | Organization-level custom additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | | | [iam_by_principals](variables.tf#L156) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable. | map(list(string)) | | {} | | | [locations](variables.tf#L163) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {} | | -| [log_sinks](variables.tf#L177) | Org-level log sinks, in name => {type, filter} format. | map(object({…})) | | {…} | | -| [org_policies_config](variables.tf#L206) | Organization policies customization. | object({…}) | | {} | | -| [outputs_location](variables.tf#L232) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | -| [project_parent_ids](variables.tf#L247) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the organization as parent. | object({…}) | | {} | | -| [workforce_identity_providers](variables.tf#L258) | Workforce Identity Federation pools. | map(object({…})) | | {} | | -| [workload_identity_providers](variables.tf#L274) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | map(object({…})) | | {} | | +| [log_sinks](variables.tf#L177) | Org-level log sinks, in name => {type, filter} format. | map(object({…})) | | {…} | | +| [org_policies_config](variables.tf#L224) | Organization policies customization. | object({…}) | | {} | | +| [outputs_location](variables.tf#L250) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | +| [project_parent_ids](variables.tf#L265) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the organization as parent. | object({…}) | | {} | | +| [workforce_identity_providers](variables.tf#L276) | Workforce Identity Federation pools. | map(object({…})) | | {} | | +| [workload_identity_providers](variables.tf#L292) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | map(object({…})) | | {} | | ## Outputs diff --git a/fast/stages/0-bootstrap/automation.tf b/fast/stages/0-bootstrap/automation.tf index 6200deab38..8463ff16b6 100644 --- a/fast/stages/0-bootstrap/automation.tf +++ b/fast/stages/0-bootstrap/automation.tf @@ -156,6 +156,24 @@ module "automation-project" { "container.googleapis.com", ] ) + + # Enable IAM data access logs to capture impersonation and service + # account token generation events. This is implemented within the + # automation project to limit log volume. For heightened security, + # consider enabling it at the organization level. A log sink within + # the organization will collect and store these logs in a logging + # bucket. See + # https://cloud.google.com/iam/docs/audit-logging#audited_operations + logging_data_access = { + "iam.googleapis.com" = { + # ADMIN_READ captures impersonation and token generation/exchanges + ADMIN_READ = [] + # enable DATA_WRITE if you want to capture configuration changes + # to IAM-related resources (roles, deny policies, service + # accounts, identity pools, etc) + # DATA_WRITE = [] + } + } } # output files bucket diff --git a/fast/stages/0-bootstrap/variables.tf b/fast/stages/0-bootstrap/variables.tf index 64978dc30f..b769f1088f 100644 --- a/fast/stages/0-bootstrap/variables.tf +++ b/fast/stages/0-bootstrap/variables.tf @@ -182,15 +182,33 @@ variable "log_sinks" { })) default = { audit-logs = { - filter = "logName:\"/logs/cloudaudit.googleapis.com%2Factivity\" OR logName:\"/logs/cloudaudit.googleapis.com%2Fsystem_event\" OR protoPayload.metadata.@type=\"type.googleapis.com/google.cloud.audit.TransparencyLog\"" + filter = <<-FILTER + log_id("cloudaudit.googleapis.com/activity") OR + log_id("cloudaudit.googleapis.com/system_event") OR + log_id("cloudaudit.googleapis.com/policy") OR + log_id("cloudaudit.googleapis.com/access_transparency") + FILTER + type = "logging" + } + iam = { + filter = <<-FILTER + protoPayload.serviceName="iamcredentials.googleapis.com" OR + protoPayload.serviceName="iam.googleapis.com" OR + protoPayload.serviceName="sts.googleapis.com" + FILTER type = "logging" } vpc-sc = { - filter = "protoPayload.metadata.@type=\"type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata\"" + filter = <<-FILTER + protoPayload.metadata.@type:"type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata" + FILTER type = "logging" } workspace-audit-logs = { - filter = "logName:\"/logs/cloudaudit.googleapis.com%2Fdata_access\" and protoPayload.serviceName:\"login.googleapis.com\"" + filter = <<-FILTER + log_id("cloudaudit.googleapis.com/data_access") + protoPayload.serviceName:"login.googleapis.com" + FILTER type = "logging" } } diff --git a/tests/fast/stages/s0_bootstrap/checklist.yaml b/tests/fast/stages/s0_bootstrap/checklist.yaml index 91fe60eb76..8c24a1fdfe 100644 --- a/tests/fast/stages/s0_bootstrap/checklist.yaml +++ b/tests/fast/stages/s0_bootstrap/checklist.yaml @@ -360,15 +360,15 @@ counts: google_bigquery_dataset: 1 google_bigquery_default_service_account: 3 google_essential_contacts_contact: 3 - google_logging_organization_sink: 3 - google_logging_project_bucket_config: 3 + google_logging_organization_sink: 4 + google_logging_project_bucket_config: 4 google_org_policy_policy: 22 google_organization_iam_binding: 27 google_organization_iam_custom_role: 7 google_organization_iam_member: 35 google_project: 3 google_project_iam_binding: 19 - google_project_iam_member: 6 + google_project_iam_member: 7 google_project_service: 31 google_project_service_identity: 4 google_service_account: 4 @@ -380,5 +380,5 @@ counts: google_storage_project_service_account: 3 google_tags_tag_key: 1 google_tags_tag_value: 1 - modules: 17 - resources: 198 + modules: 18 + resources: 202 diff --git a/tests/fast/stages/s0_bootstrap/simple.yaml b/tests/fast/stages/s0_bootstrap/simple.yaml index 904471a061..69908ad358 100644 --- a/tests/fast/stages/s0_bootstrap/simple.yaml +++ b/tests/fast/stages/s0_bootstrap/simple.yaml @@ -39,15 +39,15 @@ counts: google_bigquery_dataset: 1 google_bigquery_default_service_account: 3 google_essential_contacts_contact: 3 - google_logging_organization_sink: 3 - google_logging_project_bucket_config: 3 + google_logging_organization_sink: 4 + google_logging_project_bucket_config: 4 google_org_policy_policy: 22 google_organization_iam_binding: 27 google_organization_iam_custom_role: 7 google_organization_iam_member: 22 google_project: 3 google_project_iam_binding: 19 - google_project_iam_member: 6 + google_project_iam_member: 7 google_project_service: 31 google_project_service_identity: 4 google_service_account: 4 @@ -60,8 +60,8 @@ counts: google_tags_tag_key: 1 google_tags_tag_value: 1 local_file: 7 - modules: 16 - resources: 189 + modules: 17 + resources: 193 outputs: custom_roles: From 2446b4dd7c48f4c118514f55d31bb1a4398b17ac Mon Sep 17 00:00:00 2001 From: Vince Gonzalez Date: Thu, 25 Apr 2024 19:14:32 -0400 Subject: [PATCH 04/31] Update README.md (#2239) --- blueprints/data-solutions/data-playground/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blueprints/data-solutions/data-playground/README.md b/blueprints/data-solutions/data-playground/README.md index a2eceeba91..ef490ab27c 100644 --- a/blueprints/data-solutions/data-playground/README.md +++ b/blueprints/data-solutions/data-playground/README.md @@ -1,6 +1,6 @@ # Data Playground -This blueprint creates a minimum viable architecture for a data experimentation project with the needed APIs enabled, VPC and Firewall set in place, BigQuesy dataset, GCS bucket and an AI notebook to get started. +This blueprint creates a minimum viable architecture for a data experimentation project with the needed APIs enabled, VPC and Firewall set in place, BigQuery dataset, GCS bucket and an AI notebook to get started. This is the high level diagram: From 64ac89d59c18e3b6b4034b3fd521cf60e33dcaba Mon Sep 17 00:00:00 2001 From: Deepak Kumar <21131061+kumadee@users.noreply.github.com> Date: Fri, 26 Apr 2024 09:17:48 +0200 Subject: [PATCH 05/31] fix: allow disabling node autoprovisioning (#2238) - This fix allows a GKE Standard cluster to be configured with no auto-provisioned node pool, but allow setting autocluster profile for user-provisioned node pools like created via `gke-nodepool` module. Co-authored-by: Julio Castillo --- modules/gke-cluster-standard/README.md | 42 +++++++++++------------ modules/gke-cluster-standard/main.tf | 2 +- modules/gke-cluster-standard/variables.tf | 1 + 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/modules/gke-cluster-standard/README.md b/modules/gke-cluster-standard/README.md index 286b20a3dc..c53017267f 100644 --- a/modules/gke-cluster-standard/README.md +++ b/modules/gke-cluster-standard/README.md @@ -310,28 +310,28 @@ module "cluster-1" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [location](variables.tf#L233) | Cluster zone or region. | string | ✓ | | -| [name](variables.tf#L368) | Cluster name. | string | ✓ | | -| [project_id](variables.tf#L404) | Cluster project id. | string | ✓ | | -| [vpc_config](variables.tf#L415) | VPC-level configuration. | object({…}) | ✓ | | +| [location](variables.tf#L234) | Cluster zone or region. | string | ✓ | | +| [name](variables.tf#L369) | Cluster name. | string | ✓ | | +| [project_id](variables.tf#L405) | Cluster project id. | string | ✓ | | +| [vpc_config](variables.tf#L416) | VPC-level configuration. | object({…}) | ✓ | | | [backup_configs](variables.tf#L17) | Configuration for Backup for GKE. | object({…}) | | {} | -| [cluster_autoscaling](variables.tf#L38) | Enable and configure limits for Node Auto-Provisioning with Cluster Autoscaler. | object({…}) | | null | -| [default_nodepool](variables.tf#L116) | Enable default nodepool. | object({…}) | | {} | -| [deletion_protection](variables.tf#L134) | Whether or not to allow Terraform to destroy the cluster. Unless this field is set to false in Terraform state, a terraform destroy or terraform apply that would delete the cluster will fail. | bool | | true | -| [description](variables.tf#L141) | Cluster description. | string | | null | -| [enable_addons](variables.tf#L147) | Addons enabled in the cluster (true means enabled). | object({…}) | | {…} | -| [enable_features](variables.tf#L171) | Enable cluster-level features. Certain features allow configuration. | object({…}) | | {…} | -| [issue_client_certificate](variables.tf#L221) | Enable issuing client certificate. | bool | | false | -| [labels](variables.tf#L227) | Cluster resource labels. | map(string) | | null | -| [logging_config](variables.tf#L238) | Logging configuration. | object({…}) | | {} | -| [maintenance_config](variables.tf#L259) | Maintenance window configuration. | object({…}) | | {…} | -| [max_pods_per_node](variables.tf#L282) | Maximum number of pods per node in this cluster. | number | | 110 | -| [min_master_version](variables.tf#L288) | Minimum version of the master, defaults to the version of the most recent official release. | string | | null | -| [monitoring_config](variables.tf#L294) | Monitoring configuration. Google Cloud Managed Service for Prometheus is enabled by default. | object({…}) | | {} | -| [node_config](variables.tf#L373) | Node-level configuration. | object({…}) | | {} | -| [node_locations](variables.tf#L383) | Zones in which the cluster's nodes are located. | list(string) | | [] | -| [private_cluster_config](variables.tf#L390) | Private cluster configuration. | object({…}) | | null | -| [release_channel](variables.tf#L409) | Release channel for GKE upgrades. | string | | null | +| [cluster_autoscaling](variables.tf#L38) | Enable and configure limits for Node Auto-Provisioning with Cluster Autoscaler. | object({…}) | | null | +| [default_nodepool](variables.tf#L117) | Enable default nodepool. | object({…}) | | {} | +| [deletion_protection](variables.tf#L135) | Whether or not to allow Terraform to destroy the cluster. Unless this field is set to false in Terraform state, a terraform destroy or terraform apply that would delete the cluster will fail. | bool | | true | +| [description](variables.tf#L142) | Cluster description. | string | | null | +| [enable_addons](variables.tf#L148) | Addons enabled in the cluster (true means enabled). | object({…}) | | {…} | +| [enable_features](variables.tf#L172) | Enable cluster-level features. Certain features allow configuration. | object({…}) | | {…} | +| [issue_client_certificate](variables.tf#L222) | Enable issuing client certificate. | bool | | false | +| [labels](variables.tf#L228) | Cluster resource labels. | map(string) | | null | +| [logging_config](variables.tf#L239) | Logging configuration. | object({…}) | | {} | +| [maintenance_config](variables.tf#L260) | Maintenance window configuration. | object({…}) | | {…} | +| [max_pods_per_node](variables.tf#L283) | Maximum number of pods per node in this cluster. | number | | 110 | +| [min_master_version](variables.tf#L289) | Minimum version of the master, defaults to the version of the most recent official release. | string | | null | +| [monitoring_config](variables.tf#L295) | Monitoring configuration. Google Cloud Managed Service for Prometheus is enabled by default. | object({…}) | | {} | +| [node_config](variables.tf#L374) | Node-level configuration. | object({…}) | | {} | +| [node_locations](variables.tf#L384) | Zones in which the cluster's nodes are located. | list(string) | | [] | +| [private_cluster_config](variables.tf#L391) | Private cluster configuration. | object({…}) | | null | +| [release_channel](variables.tf#L410) | Release channel for GKE upgrades. | string | | null | ## Outputs diff --git a/modules/gke-cluster-standard/main.tf b/modules/gke-cluster-standard/main.tf index 6ab4c9d881..99db49f80d 100644 --- a/modules/gke-cluster-standard/main.tf +++ b/modules/gke-cluster-standard/main.tf @@ -132,7 +132,7 @@ resource "google_container_cluster" "cluster" { dynamic "cluster_autoscaling" { for_each = local.cas == null ? [] : [""] content { - enabled = true + enabled = var.cluster_autoscaling.enabled autoscaling_profile = var.cluster_autoscaling.autoscaling_profile dynamic "auto_provisioning_defaults" { for_each = local.cas_apd != null ? [""] : [] diff --git a/modules/gke-cluster-standard/variables.tf b/modules/gke-cluster-standard/variables.tf index 6644595494..5580320f8f 100644 --- a/modules/gke-cluster-standard/variables.tf +++ b/modules/gke-cluster-standard/variables.tf @@ -38,6 +38,7 @@ variable "backup_configs" { variable "cluster_autoscaling" { description = "Enable and configure limits for Node Auto-Provisioning with Cluster Autoscaler." type = object({ + enabled = optional(bool, true) autoscaling_profile = optional(string, "BALANCED") auto_provisioning_defaults = optional(object({ boot_disk_kms_key = optional(string) From d831d328648b1a539215360f91f4e0f9f3c4e1ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Niesiob=C4=99dzki?= Date: Sat, 27 Apr 2024 07:07:04 +0000 Subject: [PATCH 06/31] Use default labels on pubsub subscription when no override is provided --- modules/pubsub/README.md | 1 + modules/pubsub/main.tf | 2 +- tests/modules/pubsub/examples/subscriptions.yaml | 6 ++++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/pubsub/README.md b/modules/pubsub/README.md index 6d924d90d2..4aaa4a4812 100644 --- a/modules/pubsub/README.md +++ b/modules/pubsub/README.md @@ -60,6 +60,7 @@ module "pubsub" { source = "./fabric/modules/pubsub" project_id = var.project_id name = "my-topic" + labels = { test = "default" } subscriptions = { test-pull = {} test-pull-override = { diff --git a/modules/pubsub/main.tf b/modules/pubsub/main.tf index b65d372f11..a090f861b0 100644 --- a/modules/pubsub/main.tf +++ b/modules/pubsub/main.tf @@ -54,7 +54,7 @@ resource "google_pubsub_subscription" "default" { project = var.project_id name = each.key topic = google_pubsub_topic.default.name - labels = each.value.labels + labels = coalesce(each.value.labels, var.labels) ack_deadline_seconds = each.value.ack_deadline_seconds message_retention_duration = each.value.message_retention_duration retain_acked_messages = each.value.retain_acked_messages diff --git a/tests/modules/pubsub/examples/subscriptions.yaml b/tests/modules/pubsub/examples/subscriptions.yaml index 89acf08b22..267f4ebc5c 100644 --- a/tests/modules/pubsub/examples/subscriptions.yaml +++ b/tests/modules/pubsub/examples/subscriptions.yaml @@ -20,7 +20,8 @@ values: enable_exactly_once_delivery: false enable_message_ordering: false filter: null - labels: null + labels: + test: default message_retention_duration: 604800s name: test-pull project: project-id @@ -52,7 +53,8 @@ values: topic: my-topic module.pubsub.google_pubsub_topic.default: kms_key_name: null - labels: null + labels: + test: default message_retention_duration: null name: my-topic project: project-id From a95e681f059af9e112636befd03efb124439115f Mon Sep 17 00:00:00 2001 From: apichick Date: Sun, 28 Apr 2024 12:11:07 +0200 Subject: [PATCH 07/31] Removed BFD settings from net-vpn-ha module as it is not supported (#2244) * Removed bfd settings from net-vpn-ha as it is not supported * Removed bfd settings from net-vpn-ha as it is not supported --- modules/net-vpn-ha/README.md | 6 +++--- modules/net-vpn-ha/main.tf | 9 --------- modules/net-vpn-ha/variables.tf | 6 ------ 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/modules/net-vpn-ha/README.md b/modules/net-vpn-ha/README.md index 2de72f54ea..164e009f7e 100644 --- a/modules/net-vpn-ha/README.md +++ b/modules/net-vpn-ha/README.md @@ -213,9 +213,9 @@ module "vpn_ha" { | [region](variables.tf#L52) | Region used for resources. | string | ✓ | | | [router_config](variables.tf#L57) | Cloud Router configuration for the VPN. If you want to reuse an existing router, set create to false and use name to specify the desired router. | object({…}) | ✓ | | | [peer_gateways](variables.tf#L27) | Configuration of the (external or GCP) peer gateway. | map(object({…})) | | {} | -| [tunnels](variables.tf#L72) | VPN tunnel configurations. | map(object({…})) | | {} | -| [vpn_gateway](variables.tf#L114) | HA VPN Gateway Self Link for using an existing HA VPN Gateway. Ignored if `vpn_gateway_create` is set to `true`. | string | | null | -| [vpn_gateway_create](variables.tf#L120) | Create HA VPN Gateway. Set to null to avoid creation. | object({…}) | | {} | +| [tunnels](variables.tf#L72) | VPN tunnel configurations. | map(object({…})) | | {} | +| [vpn_gateway](variables.tf#L108) | HA VPN Gateway Self Link for using an existing HA VPN Gateway. Ignored if `vpn_gateway_create` is set to `true`. | string | | null | +| [vpn_gateway_create](variables.tf#L114) | Create HA VPN Gateway. Set to null to avoid creation. | object({…}) | | {} | ## Outputs diff --git a/modules/net-vpn-ha/main.tf b/modules/net-vpn-ha/main.tf index 20af29015a..bbb7ca0295 100644 --- a/modules/net-vpn-ha/main.tf +++ b/modules/net-vpn-ha/main.tf @@ -117,15 +117,6 @@ resource "google_compute_router_peer" "bgp_peer" { description = range.value } } - dynamic "bfd" { - for_each = each.value.bgp_peer.bfd != null ? [each.value.bgp_peer.bfd] : [] - content { - session_initialization_mode = bfd.value.session_initialization_mode - min_receive_interval = bfd.value.min_receive_interval - min_transmit_interval = bfd.value.min_transmit_interval - multiplier = bfd.value.multiplier - } - } dynamic "md5_authentication_key" { for_each = each.value.bgp_peer.md5_authentication_key != null ? toset([each.value.bgp_peer.md5_authentication_key]) : [] content { diff --git a/modules/net-vpn-ha/variables.tf b/modules/net-vpn-ha/variables.tf index d507c89881..ba86eee6e9 100644 --- a/modules/net-vpn-ha/variables.tf +++ b/modules/net-vpn-ha/variables.tf @@ -76,12 +76,6 @@ variable "tunnels" { address = string asn = number route_priority = optional(number, 1000) - bfd = optional(object({ - min_receive_interval = optional(number) - min_transmit_interval = optional(number) - multiplier = optional(number) - session_initialization_mode = optional(string, "ACTIVE") - })) custom_advertise = optional(object({ all_subnets = bool all_vpc_subnets = bool From ab174274de35c6b73f44e23d312c86a25c729d72 Mon Sep 17 00:00:00 2001 From: apichick Date: Sun, 28 Apr 2024 17:31:42 +0200 Subject: [PATCH 08/31] Added new attributes Apigee organization and bumped up providers version (#2243) --- .../patterns/autopilot-cluster/versions.tf | 4 +- blueprints/gke/patterns/batch/versions.tf | 4 +- blueprints/gke/patterns/kafka/versions.tf | 4 +- blueprints/gke/patterns/mysql/versions.tf | 4 +- .../gke/patterns/redis-cluster/versions.tf | 4 +- default-versions.tf | 4 +- .../alloydb-instance/versions.tf | 4 +- .../net-neg/versions.tf | 4 +- .../project-iam-magic/versions.tf | 4 +- modules/analytics-hub/versions.tf | 4 +- modules/api-gateway/versions.tf | 4 +- modules/apigee/README.md | 8 +-- modules/apigee/main.tf | 33 ++++++++---- modules/apigee/variables.tf | 50 ++++++++++--------- modules/apigee/versions.tf | 4 +- modules/artifact-registry/versions.tf | 4 +- modules/bigquery-dataset/versions.tf | 4 +- modules/bigtable-instance/versions.tf | 4 +- modules/billing-account/versions.tf | 4 +- modules/binauthz/versions.tf | 4 +- .../__need_fixing/onprem/versions.tf | 4 +- .../__need_fixing/squid/versions.tf | 4 +- .../coredns/versions.tf | 4 +- .../cos-generic-metadata/versions.tf | 4 +- .../envoy-sni-dyn-fwd-proxy/versions.tf | 4 +- .../envoy-traffic-director/versions.tf | 4 +- .../cloud-config-container/mysql/versions.tf | 4 +- .../nginx-tls/versions.tf | 4 +- .../cloud-config-container/nginx/versions.tf | 4 +- .../simple-nva/versions.tf | 4 +- modules/cloud-function-v1/versions.tf | 4 +- modules/cloud-function-v2/versions.tf | 4 +- modules/cloud-identity-group/versions.tf | 4 +- modules/cloud-run-v2/versions.tf | 4 +- modules/cloud-run/versions.tf | 4 +- modules/cloudsql-instance/versions.tf | 4 +- modules/compute-mig/versions.tf | 4 +- modules/compute-vm/versions.tf | 4 +- modules/container-registry/versions.tf | 4 +- modules/data-catalog-policy-tag/versions.tf | 4 +- modules/data-catalog-tag-template/versions.tf | 4 +- modules/data-catalog-tag/versions.tf | 4 +- modules/dataform-repository/versions.tf | 4 +- modules/datafusion/versions.tf | 4 +- modules/dataplex-datascan/versions.tf | 4 +- modules/dataplex/versions.tf | 4 +- modules/dataproc/versions.tf | 4 +- modules/dns-response-policy/versions.tf | 4 +- modules/dns/versions.tf | 4 +- modules/endpoints/versions.tf | 4 +- modules/folder/versions.tf | 4 +- modules/gcs/versions.tf | 4 +- modules/gcve-private-cloud/versions.tf | 4 +- modules/gke-cluster-autopilot/versions.tf | 4 +- modules/gke-cluster-standard/versions.tf | 4 +- modules/gke-hub/versions.tf | 4 +- modules/gke-nodepool/versions.tf | 4 +- modules/iam-service-account/versions.tf | 4 +- modules/kms/versions.tf | 4 +- modules/logging-bucket/versions.tf | 4 +- modules/ncc-spoke-ra/versions.tf | 4 +- modules/net-address/versions.tf | 4 +- modules/net-cloudnat/versions.tf | 4 +- modules/net-firewall-policy/versions.tf | 4 +- .../net-ipsec-over-interconnect/versions.tf | 4 +- modules/net-lb-app-ext-regional/versions.tf | 4 +- modules/net-lb-app-ext/versions.tf | 4 +- .../net-lb-app-int-cross-region/versions.tf | 4 +- modules/net-lb-app-int/versions.tf | 4 +- modules/net-lb-ext/versions.tf | 4 +- modules/net-lb-int/versions.tf | 4 +- modules/net-lb-proxy-int/versions.tf | 4 +- modules/net-swp/versions.tf | 4 +- modules/net-vlan-attachment/versions.tf | 4 +- modules/net-vpc-firewall/versions.tf | 4 +- modules/net-vpc-peering/versions.tf | 4 +- modules/net-vpc/versions.tf | 4 +- modules/net-vpn-dynamic/versions.tf | 4 +- modules/net-vpn-ha/versions.tf | 4 +- modules/net-vpn-static/versions.tf | 4 +- modules/organization/versions.tf | 4 +- modules/project/versions.tf | 4 +- modules/projects-data-source/versions.tf | 4 +- modules/pubsub/versions.tf | 4 +- modules/secret-manager/versions.tf | 4 +- modules/service-directory/versions.tf | 4 +- modules/source-repository/versions.tf | 4 +- modules/vpc-sc/versions.tf | 4 +- modules/workstation-cluster/versions.tf | 4 +- tests/examples_e2e/setup_module/versions.tf | 4 +- 90 files changed, 229 insertions(+), 210 deletions(-) diff --git a/blueprints/gke/patterns/autopilot-cluster/versions.tf b/blueprints/gke/patterns/autopilot-cluster/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/blueprints/gke/patterns/autopilot-cluster/versions.tf +++ b/blueprints/gke/patterns/autopilot-cluster/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/blueprints/gke/patterns/batch/versions.tf b/blueprints/gke/patterns/batch/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/blueprints/gke/patterns/batch/versions.tf +++ b/blueprints/gke/patterns/batch/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/blueprints/gke/patterns/kafka/versions.tf b/blueprints/gke/patterns/kafka/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/blueprints/gke/patterns/kafka/versions.tf +++ b/blueprints/gke/patterns/kafka/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/blueprints/gke/patterns/mysql/versions.tf b/blueprints/gke/patterns/mysql/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/blueprints/gke/patterns/mysql/versions.tf +++ b/blueprints/gke/patterns/mysql/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/blueprints/gke/patterns/redis-cluster/versions.tf b/blueprints/gke/patterns/redis-cluster/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/blueprints/gke/patterns/redis-cluster/versions.tf +++ b/blueprints/gke/patterns/redis-cluster/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/default-versions.tf b/default-versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/default-versions.tf +++ b/default-versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/__experimental_deprecated/alloydb-instance/versions.tf b/modules/__experimental_deprecated/alloydb-instance/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/__experimental_deprecated/alloydb-instance/versions.tf +++ b/modules/__experimental_deprecated/alloydb-instance/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/__experimental_deprecated/net-neg/versions.tf b/modules/__experimental_deprecated/net-neg/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/__experimental_deprecated/net-neg/versions.tf +++ b/modules/__experimental_deprecated/net-neg/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/__experimental_deprecated/project-iam-magic/versions.tf b/modules/__experimental_deprecated/project-iam-magic/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/__experimental_deprecated/project-iam-magic/versions.tf +++ b/modules/__experimental_deprecated/project-iam-magic/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/analytics-hub/versions.tf b/modules/analytics-hub/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/analytics-hub/versions.tf +++ b/modules/analytics-hub/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/api-gateway/versions.tf b/modules/api-gateway/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/api-gateway/versions.tf +++ b/modules/api-gateway/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/apigee/README.md b/modules/apigee/README.md index 24c6f4166a..ebae675f31 100644 --- a/modules/apigee/README.md +++ b/modules/apigee/README.md @@ -359,13 +359,13 @@ module "apigee" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [project_id](variables.tf#L126) | Project ID. | string | ✓ | | +| [project_id](variables.tf#L130) | Project ID. | string | ✓ | | | [addons_config](variables.tf#L17) | Addons configuration. | object({…}) | | null | | [endpoint_attachments](variables.tf#L29) | Endpoint attachments. | map(object({…})) | | {} | | [envgroups](variables.tf#L39) | Environment groups (NAME => [HOSTNAMES]). | map(list(string)) | | {} | -| [environments](variables.tf#L46) | Environments. | map(object({…})) | | {} | -| [instances](variables.tf#L73) | Instances ([REGION] => [INSTANCE]). | map(object({…})) | | {} | -| [organization](variables.tf#L98) | Apigee organization. If set to null the organization must already exist. | object({…}) | | null | +| [environments](variables.tf#L46) | Environments. | map(object({…})) | | {} | +| [instances](variables.tf#L73) | Instances ([REGION] => [INSTANCE]). | map(object({…})) | | {} | +| [organization](variables.tf#L98) | Apigee organization. If set to null the organization must already exist. | object({…}) | | null | ## Outputs diff --git a/modules/apigee/main.tf b/modules/apigee/main.tf index bb0417b279..afbc1e5af6 100644 --- a/modules/apigee/main.tf +++ b/modules/apigee/main.tf @@ -20,15 +20,30 @@ locals { } resource "google_apigee_organization" "organization" { - count = var.organization == null ? 0 : 1 - analytics_region = var.organization.analytics_region - project_id = var.project_id - authorized_network = var.organization.authorized_network - billing_type = var.organization.billing_type - runtime_type = var.organization.runtime_type - runtime_database_encryption_key_name = var.organization.database_encryption_key - retention = var.organization.retention - disable_vpc_peering = var.organization.disable_vpc_peering + count = var.organization == null ? 0 : 1 + analytics_region = var.organization.analytics_region + project_id = var.project_id + authorized_network = var.organization.authorized_network + billing_type = var.organization.billing_type + runtime_type = var.organization.runtime_type + runtime_database_encryption_key_name = var.organization.database_encryption_key + retention = var.organization.retention + disable_vpc_peering = var.organization.disable_vpc_peering + api_consumer_data_location = var.organization.api_consumer_data_location + api_consumer_data_encryption_key_name = var.organization.api_consumer_data_encryption_key + control_plane_encryption_key_name = var.organization.control_plane_encryption_key + dynamic "properties" { + for_each = length(var.organization.properties) > 0 ? [""] : [] + content { + dynamic "property" { + for_each = var.organization.properties + content { + name = properties.key + value = properties.value + } + } + } + } } resource "google_apigee_envgroup" "envgroups" { diff --git a/modules/apigee/variables.tf b/modules/apigee/variables.tf index 027ad05e47..6ce9fdcf1f 100644 --- a/modules/apigee/variables.tf +++ b/modules/apigee/variables.tf @@ -46,16 +46,12 @@ variable "envgroups" { variable "environments" { description = "Environments." type = map(object({ - display_name = optional(string) + api_proxy_type = optional(string) description = optional(string, "Terraform-managed") + display_name = optional(string) deployment_type = optional(string) - api_proxy_type = optional(string) - type = optional(string) - node_config = optional(object({ - min_node_count = optional(number) - max_node_count = optional(number) - })) - iam = optional(map(list(string)), {}) + envgroups = optional(list(string), []) + iam = optional(map(list(string)), {}) iam_bindings = optional(map(object({ role = string members = list(string) @@ -64,7 +60,11 @@ variable "environments" { role = string member = string })), {}) - envgroups = optional(list(string), []) + node_config = optional(object({ + min_node_count = optional(number) + max_node_count = optional(number) + })) + type = optional(string) })) default = {} nullable = false @@ -73,15 +73,15 @@ variable "environments" { variable "instances" { description = "Instances ([REGION] => [INSTANCE])." type = map(object({ - name = optional(string) - display_name = optional(string) + consumer_accept_list = optional(list(string)) description = optional(string, "Terraform-managed") - runtime_ip_cidr_range = optional(string) - troubleshooting_ip_cidr_range = optional(string) disk_encryption_key = optional(string) - consumer_accept_list = optional(list(string)) + display_name = optional(string) enable_nat = optional(bool, false) environments = optional(list(string), []) + name = optional(string) + runtime_ip_cidr_range = optional(string) + troubleshooting_ip_cidr_range = optional(string) })) validation { condition = alltrue([ @@ -98,15 +98,19 @@ variable "instances" { variable "organization" { description = "Apigee organization. If set to null the organization must already exist." type = object({ - display_name = optional(string) - description = optional(string, "Terraform-managed") - authorized_network = optional(string) - runtime_type = optional(string, "CLOUD") - billing_type = optional(string) - database_encryption_key = optional(string) - analytics_region = optional(string, "europe-west1") - retention = optional(string) - disable_vpc_peering = optional(bool, false) + analytics_region = optional(string) + api_consumer_data_encryption_key = optional(string) + api_consumer_data_location = optional(string) + authorized_network = optional(string) + billing_type = optional(string) + control_plane_encryption_key = optional(string) + database_encryption_key = optional(string) + description = optional(string, "Terraform-managed") + disable_vpc_peering = optional(bool, false) + display_name = optional(string) + properties = optional(map(string), {}) + runtime_type = optional(string, "CLOUD") + retention = optional(string) }) validation { condition = var.organization == null || ( diff --git a/modules/apigee/versions.tf b/modules/apigee/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/apigee/versions.tf +++ b/modules/apigee/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/artifact-registry/versions.tf b/modules/artifact-registry/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/artifact-registry/versions.tf +++ b/modules/artifact-registry/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/bigquery-dataset/versions.tf b/modules/bigquery-dataset/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/bigquery-dataset/versions.tf +++ b/modules/bigquery-dataset/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/bigtable-instance/versions.tf b/modules/bigtable-instance/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/bigtable-instance/versions.tf +++ b/modules/bigtable-instance/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/billing-account/versions.tf b/modules/billing-account/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/billing-account/versions.tf +++ b/modules/billing-account/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/binauthz/versions.tf b/modules/binauthz/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/binauthz/versions.tf +++ b/modules/binauthz/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/cloud-config-container/__need_fixing/onprem/versions.tf b/modules/cloud-config-container/__need_fixing/onprem/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/cloud-config-container/__need_fixing/onprem/versions.tf +++ b/modules/cloud-config-container/__need_fixing/onprem/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/cloud-config-container/__need_fixing/squid/versions.tf b/modules/cloud-config-container/__need_fixing/squid/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/cloud-config-container/__need_fixing/squid/versions.tf +++ b/modules/cloud-config-container/__need_fixing/squid/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/cloud-config-container/coredns/versions.tf b/modules/cloud-config-container/coredns/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/cloud-config-container/coredns/versions.tf +++ b/modules/cloud-config-container/coredns/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/cloud-config-container/cos-generic-metadata/versions.tf b/modules/cloud-config-container/cos-generic-metadata/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/cloud-config-container/cos-generic-metadata/versions.tf +++ b/modules/cloud-config-container/cos-generic-metadata/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/cloud-config-container/envoy-sni-dyn-fwd-proxy/versions.tf b/modules/cloud-config-container/envoy-sni-dyn-fwd-proxy/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/cloud-config-container/envoy-sni-dyn-fwd-proxy/versions.tf +++ b/modules/cloud-config-container/envoy-sni-dyn-fwd-proxy/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/cloud-config-container/envoy-traffic-director/versions.tf b/modules/cloud-config-container/envoy-traffic-director/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/cloud-config-container/envoy-traffic-director/versions.tf +++ b/modules/cloud-config-container/envoy-traffic-director/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/cloud-config-container/mysql/versions.tf b/modules/cloud-config-container/mysql/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/cloud-config-container/mysql/versions.tf +++ b/modules/cloud-config-container/mysql/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/cloud-config-container/nginx-tls/versions.tf b/modules/cloud-config-container/nginx-tls/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/cloud-config-container/nginx-tls/versions.tf +++ b/modules/cloud-config-container/nginx-tls/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/cloud-config-container/nginx/versions.tf b/modules/cloud-config-container/nginx/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/cloud-config-container/nginx/versions.tf +++ b/modules/cloud-config-container/nginx/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/cloud-config-container/simple-nva/versions.tf b/modules/cloud-config-container/simple-nva/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/cloud-config-container/simple-nva/versions.tf +++ b/modules/cloud-config-container/simple-nva/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/cloud-function-v1/versions.tf b/modules/cloud-function-v1/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/cloud-function-v1/versions.tf +++ b/modules/cloud-function-v1/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/cloud-function-v2/versions.tf b/modules/cloud-function-v2/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/cloud-function-v2/versions.tf +++ b/modules/cloud-function-v2/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/cloud-identity-group/versions.tf b/modules/cloud-identity-group/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/cloud-identity-group/versions.tf +++ b/modules/cloud-identity-group/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/cloud-run-v2/versions.tf b/modules/cloud-run-v2/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/cloud-run-v2/versions.tf +++ b/modules/cloud-run-v2/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/cloud-run/versions.tf b/modules/cloud-run/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/cloud-run/versions.tf +++ b/modules/cloud-run/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/cloudsql-instance/versions.tf b/modules/cloudsql-instance/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/cloudsql-instance/versions.tf +++ b/modules/cloudsql-instance/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/compute-mig/versions.tf b/modules/compute-mig/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/compute-mig/versions.tf +++ b/modules/compute-mig/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/compute-vm/versions.tf b/modules/compute-vm/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/compute-vm/versions.tf +++ b/modules/compute-vm/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/container-registry/versions.tf b/modules/container-registry/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/container-registry/versions.tf +++ b/modules/container-registry/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/data-catalog-policy-tag/versions.tf b/modules/data-catalog-policy-tag/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/data-catalog-policy-tag/versions.tf +++ b/modules/data-catalog-policy-tag/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/data-catalog-tag-template/versions.tf b/modules/data-catalog-tag-template/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/data-catalog-tag-template/versions.tf +++ b/modules/data-catalog-tag-template/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/data-catalog-tag/versions.tf b/modules/data-catalog-tag/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/data-catalog-tag/versions.tf +++ b/modules/data-catalog-tag/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/dataform-repository/versions.tf b/modules/dataform-repository/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/dataform-repository/versions.tf +++ b/modules/dataform-repository/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/datafusion/versions.tf b/modules/datafusion/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/datafusion/versions.tf +++ b/modules/datafusion/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/dataplex-datascan/versions.tf b/modules/dataplex-datascan/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/dataplex-datascan/versions.tf +++ b/modules/dataplex-datascan/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/dataplex/versions.tf b/modules/dataplex/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/dataplex/versions.tf +++ b/modules/dataplex/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/dataproc/versions.tf b/modules/dataproc/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/dataproc/versions.tf +++ b/modules/dataproc/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/dns-response-policy/versions.tf b/modules/dns-response-policy/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/dns-response-policy/versions.tf +++ b/modules/dns-response-policy/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/dns/versions.tf b/modules/dns/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/dns/versions.tf +++ b/modules/dns/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/endpoints/versions.tf b/modules/endpoints/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/endpoints/versions.tf +++ b/modules/endpoints/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/folder/versions.tf b/modules/folder/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/folder/versions.tf +++ b/modules/folder/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/gcs/versions.tf b/modules/gcs/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/gcs/versions.tf +++ b/modules/gcs/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/gcve-private-cloud/versions.tf b/modules/gcve-private-cloud/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/gcve-private-cloud/versions.tf +++ b/modules/gcve-private-cloud/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/gke-cluster-autopilot/versions.tf b/modules/gke-cluster-autopilot/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/gke-cluster-autopilot/versions.tf +++ b/modules/gke-cluster-autopilot/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/gke-cluster-standard/versions.tf b/modules/gke-cluster-standard/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/gke-cluster-standard/versions.tf +++ b/modules/gke-cluster-standard/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/gke-hub/versions.tf b/modules/gke-hub/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/gke-hub/versions.tf +++ b/modules/gke-hub/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/gke-nodepool/versions.tf b/modules/gke-nodepool/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/gke-nodepool/versions.tf +++ b/modules/gke-nodepool/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/iam-service-account/versions.tf b/modules/iam-service-account/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/iam-service-account/versions.tf +++ b/modules/iam-service-account/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/kms/versions.tf b/modules/kms/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/kms/versions.tf +++ b/modules/kms/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/logging-bucket/versions.tf b/modules/logging-bucket/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/logging-bucket/versions.tf +++ b/modules/logging-bucket/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/ncc-spoke-ra/versions.tf b/modules/ncc-spoke-ra/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/ncc-spoke-ra/versions.tf +++ b/modules/ncc-spoke-ra/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/net-address/versions.tf b/modules/net-address/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/net-address/versions.tf +++ b/modules/net-address/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/net-cloudnat/versions.tf b/modules/net-cloudnat/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/net-cloudnat/versions.tf +++ b/modules/net-cloudnat/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/net-firewall-policy/versions.tf b/modules/net-firewall-policy/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/net-firewall-policy/versions.tf +++ b/modules/net-firewall-policy/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/net-ipsec-over-interconnect/versions.tf b/modules/net-ipsec-over-interconnect/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/net-ipsec-over-interconnect/versions.tf +++ b/modules/net-ipsec-over-interconnect/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/net-lb-app-ext-regional/versions.tf b/modules/net-lb-app-ext-regional/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/net-lb-app-ext-regional/versions.tf +++ b/modules/net-lb-app-ext-regional/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/net-lb-app-ext/versions.tf b/modules/net-lb-app-ext/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/net-lb-app-ext/versions.tf +++ b/modules/net-lb-app-ext/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/net-lb-app-int-cross-region/versions.tf b/modules/net-lb-app-int-cross-region/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/net-lb-app-int-cross-region/versions.tf +++ b/modules/net-lb-app-int-cross-region/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/net-lb-app-int/versions.tf b/modules/net-lb-app-int/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/net-lb-app-int/versions.tf +++ b/modules/net-lb-app-int/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/net-lb-ext/versions.tf b/modules/net-lb-ext/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/net-lb-ext/versions.tf +++ b/modules/net-lb-ext/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/net-lb-int/versions.tf b/modules/net-lb-int/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/net-lb-int/versions.tf +++ b/modules/net-lb-int/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/net-lb-proxy-int/versions.tf b/modules/net-lb-proxy-int/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/net-lb-proxy-int/versions.tf +++ b/modules/net-lb-proxy-int/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/net-swp/versions.tf b/modules/net-swp/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/net-swp/versions.tf +++ b/modules/net-swp/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/net-vlan-attachment/versions.tf b/modules/net-vlan-attachment/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/net-vlan-attachment/versions.tf +++ b/modules/net-vlan-attachment/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/net-vpc-firewall/versions.tf b/modules/net-vpc-firewall/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/net-vpc-firewall/versions.tf +++ b/modules/net-vpc-firewall/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/net-vpc-peering/versions.tf b/modules/net-vpc-peering/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/net-vpc-peering/versions.tf +++ b/modules/net-vpc-peering/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/net-vpc/versions.tf b/modules/net-vpc/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/net-vpc/versions.tf +++ b/modules/net-vpc/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/net-vpn-dynamic/versions.tf b/modules/net-vpn-dynamic/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/net-vpn-dynamic/versions.tf +++ b/modules/net-vpn-dynamic/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/net-vpn-ha/versions.tf b/modules/net-vpn-ha/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/net-vpn-ha/versions.tf +++ b/modules/net-vpn-ha/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/net-vpn-static/versions.tf b/modules/net-vpn-static/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/net-vpn-static/versions.tf +++ b/modules/net-vpn-static/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/organization/versions.tf b/modules/organization/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/organization/versions.tf +++ b/modules/organization/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/project/versions.tf b/modules/project/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/project/versions.tf +++ b/modules/project/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/projects-data-source/versions.tf b/modules/projects-data-source/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/projects-data-source/versions.tf +++ b/modules/projects-data-source/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/pubsub/versions.tf b/modules/pubsub/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/pubsub/versions.tf +++ b/modules/pubsub/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/secret-manager/versions.tf b/modules/secret-manager/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/secret-manager/versions.tf +++ b/modules/secret-manager/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/service-directory/versions.tf b/modules/service-directory/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/service-directory/versions.tf +++ b/modules/service-directory/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/source-repository/versions.tf b/modules/source-repository/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/source-repository/versions.tf +++ b/modules/source-repository/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/vpc-sc/versions.tf b/modules/vpc-sc/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/vpc-sc/versions.tf +++ b/modules/vpc-sc/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/modules/workstation-cluster/versions.tf b/modules/workstation-cluster/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/modules/workstation-cluster/versions.tf +++ b/modules/workstation-cluster/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } diff --git a/tests/examples_e2e/setup_module/versions.tf b/tests/examples_e2e/setup_module/versions.tf index 3d6a43260a..bc9986b3c7 100644 --- a/tests/examples_e2e/setup_module/versions.tf +++ b/tests/examples_e2e/setup_module/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.24.0, < 6.0.0" # tftest + version = ">= 5.26.0, < 6.0.0" # tftest } } } From be966c4f3273beff68ad51250ca3b5ae49ecbc35 Mon Sep 17 00:00:00 2001 From: apichick Date: Sun, 28 Apr 2024 22:18:02 +0200 Subject: [PATCH 09/31] Fixed issue with service networking DNS peering (#2246) Co-authored-by: Ludovico Magnocavallo --- modules/net-vpc/psa.tf | 2 +- tests/modules/net_vpc/examples/psa-routes.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/net-vpc/psa.tf b/modules/net-vpc/psa.tf index 1e44a9accb..913cebfb3a 100644 --- a/modules/net-vpc/psa.tf +++ b/modules/net-vpc/psa.tf @@ -28,7 +28,7 @@ locals { _psa_peered_domains = flatten([ for config in local.psa_configs : [ for v in config.peered_domains : { - key = "${config.key}-${replace(v, ".", "-")}" + key = "${config.key}-${trimsuffix(replace(v, ".", "-"), "-")}" dns_suffix = v service_producer = config.service_producer } diff --git a/tests/modules/net_vpc/examples/psa-routes.yaml b/tests/modules/net_vpc/examples/psa-routes.yaml index c64353b788..a9a8bfddb3 100644 --- a/tests/modules/net_vpc/examples/psa-routes.yaml +++ b/tests/modules/net_vpc/examples/psa-routes.yaml @@ -84,9 +84,9 @@ values: - servicenetworking-googleapis-com-myrange service: servicenetworking.googleapis.com timeouts: null - module.vpc.google_service_networking_peered_dns_domain.name["servicenetworking-googleapis-com-gcp-example-com-"]: + module.vpc.google_service_networking_peered_dns_domain.name["servicenetworking-googleapis-com-gcp-example-com"]: dns_suffix: gcp.example.com. - name: servicenetworking-googleapis-com-gcp-example-com- + name: servicenetworking-googleapis-com-gcp-example-com network: my-network project: project-id service: servicenetworking.googleapis.com From e1226676fdb5f9bc918e8734ee8348c9b1968963 Mon Sep 17 00:00:00 2001 From: jnahelou Date: Tue, 30 Apr 2024 19:21:35 +0200 Subject: [PATCH 10/31] Added missing identity when connectors API is enabled (#2248) --- modules/project/service-agents.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/project/service-agents.yaml b/modules/project/service-agents.yaml index 66c95a4640..455287e807 100644 --- a/modules/project/service-agents.yaml +++ b/modules/project/service-agents.yaml @@ -121,6 +121,7 @@ service_agent: "service-%s@gcp-sa-anthossupport.iam.gserviceaccount.com" - name: "connectors" service_agent: "service-%s@gcp-sa-connectors.iam.gserviceaccount.com" + jit: true - name: "contactcenteraiplatform" service_agent: "service-%s@gcp-sa-ccaip.iam.gserviceaccount.com" - name: "contactcenterinsights" From 27a055a9cbffda216250af736ec2a68241bec12e Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Wed, 1 May 2024 18:50:30 +0200 Subject: [PATCH 11/31] fix factory ingress policies (#2251) --- modules/vpc-sc/README.md | 23 +++++++++++++++++++--- modules/vpc-sc/factory.tf | 2 +- tests/modules/vpc_sc/examples/factory.yaml | 20 ++++++++++++++++++- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/modules/vpc-sc/README.md b/modules/vpc-sc/README.md index 2bdeebe9fe..fb99c10657 100644 --- a/modules/vpc-sc/README.md +++ b/modules/vpc-sc/README.md @@ -233,7 +233,7 @@ module "test" { resources = ["projects/11111", "projects/111111"] restricted_services = ["storage.googleapis.com"] egress_policies = ["gcs-sa-foo"] - ingress_policies = ["sa-tf-test"] + ingress_policies = ["sa-tf-test-geo", "sa-tf-test"] vpc_accessible_services = { allowed_services = ["storage.googleapis.com"] enable_restriction = true @@ -242,7 +242,7 @@ module "test" { } } } -# tftest modules=1 resources=3 files=a1,a2,e1,i1 inventory=factory.yaml +# tftest modules=1 resources=3 files=a1,a2,e1,i1,i2 inventory=factory.yaml ``` ```yaml @@ -282,12 +282,29 @@ from: - serviceAccount:test-tf@myproject.iam.gserviceaccount.com to: operations: - - service_name: "*" + - service_name: compute.googleapis.com + method_selectors: + - ProjectsService.Get + - RegionsService.Get resources: - "*" # tftest-file id=i1 path=data/ingress-policies/sa-tf-test.yaml ``` +```yaml +from: + access_levels: + - geo-it + identities: + - serviceAccount:test-tf@myproject.iam.gserviceaccount.com +to: + operations: + - service_name: "*" + resources: + - projects/1234567890 +# tftest-file id=i2 path=data/ingress-policies/sa-tf-test-geo.yaml +``` + ## Notes - To remove an access level, first remove the binding between perimeter and the access level in `status` and/or `spec` without removing the access level itself. Once you have run `terraform apply`, you'll then be able to remove the access level and run `terraform apply` again. diff --git a/modules/vpc-sc/factory.tf b/modules/vpc-sc/factory.tf index eca5867a7e..d8a0a53622 100644 --- a/modules/vpc-sc/factory.tf +++ b/modules/vpc-sc/factory.tf @@ -74,7 +74,7 @@ locals { }, try(v.from, {})) to = { operations = [ - for o in try(v.operations, []) : merge({ + for o in try(v.to.operations, []) : merge({ method_selectors = [] permission_selectors = [] service_name = null diff --git a/tests/modules/vpc_sc/examples/factory.yaml b/tests/modules/vpc_sc/examples/factory.yaml index 475c4d1e89..4496566709 100644 --- a/tests/modules/vpc_sc/examples/factory.yaml +++ b/tests/modules/vpc_sc/examples/factory.yaml @@ -81,9 +81,27 @@ values: - access_level: '*' resource: null ingress_to: - - operations: [] + - operations: + - method_selectors: + - method: ProjectsService.Get + permission: null + - method: RegionsService.Get + permission: null + service_name: compute.googleapis.com resources: - '*' + - ingress_from: + - identities: + - serviceAccount:test-tf@myproject.iam.gserviceaccount.com + identity_type: null + sources: + - resource: null + ingress_to: + - operations: + - method_selectors: [] + service_name: '*' + resources: + - projects/1234567890 resources: - projects/11111 - projects/111111 From fdcd309729b6c99f4ce2672e71d64df430529ff8 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Wed, 1 May 2024 20:20:21 +0200 Subject: [PATCH 12/31] add support for labels to GKE backup plans (#2252) --- modules/gke-cluster-autopilot/README.md | 38 +++++++++---------- modules/gke-cluster-autopilot/main.tf | 1 + modules/gke-cluster-autopilot/variables.tf | 1 + modules/gke-cluster-standard/README.md | 44 +++++++++++----------- modules/gke-cluster-standard/main.tf | 1 + modules/gke-cluster-standard/variables.tf | 1 + 6 files changed, 45 insertions(+), 41 deletions(-) diff --git a/modules/gke-cluster-autopilot/README.md b/modules/gke-cluster-autopilot/README.md index bf8affbe51..0091d6e04d 100644 --- a/modules/gke-cluster-autopilot/README.md +++ b/modules/gke-cluster-autopilot/README.md @@ -206,25 +206,25 @@ module "cluster-1" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [location](variables.tf#L112) | Autopilot clusters are always regional. | string | ✓ | | -| [name](variables.tf#L189) | Cluster name. | string | ✓ | | -| [project_id](variables.tf#L225) | Cluster project ID. | string | ✓ | | -| [vpc_config](variables.tf#L241) | VPC-level configuration. | object({…}) | ✓ | | -| [backup_configs](variables.tf#L17) | Configuration for Backup for GKE. | object({…}) | | {} | -| [deletion_protection](variables.tf#L37) | Whether or not to allow Terraform to destroy the cluster. Unless this field is set to false in Terraform state, a terraform destroy or terraform apply that would delete the cluster will fail. | bool | | true | -| [description](variables.tf#L44) | Cluster description. | string | | null | -| [enable_addons](variables.tf#L50) | Addons enabled in the cluster (true means enabled). | object({…}) | | {} | -| [enable_features](variables.tf#L64) | Enable cluster-level features. Certain features allow configuration. | object({…}) | | {} | -| [issue_client_certificate](variables.tf#L100) | Enable issuing client certificate. | bool | | false | -| [labels](variables.tf#L106) | Cluster resource labels. | map(string) | | null | -| [logging_config](variables.tf#L117) | Logging configuration. | object({…}) | | {} | -| [maintenance_config](variables.tf#L128) | Maintenance window configuration. | object({…}) | | {…} | -| [min_master_version](variables.tf#L151) | Minimum version of the master, defaults to the version of the most recent official release. | string | | null | -| [monitoring_config](variables.tf#L157) | Monitoring configuration. System metrics collection cannot be disabled. Control plane metrics are optional. Kube state metrics are optional. Google Cloud Managed Service for Prometheus is enabled by default. | object({…}) | | {} | -| [node_config](variables.tf#L194) | Configuration for nodes and nodepools. | object({…}) | | {} | -| [node_locations](variables.tf#L204) | Zones in which the cluster's nodes are located. | list(string) | | [] | -| [private_cluster_config](variables.tf#L211) | Private cluster configuration. | object({…}) | | null | -| [release_channel](variables.tf#L230) | Release channel for GKE upgrades. Clusters created in the Autopilot mode must use a release channel. Choose between \"RAPID\", \"REGULAR\", and \"STABLE\". | string | | "REGULAR" | +| [location](variables.tf#L113) | Autopilot clusters are always regional. | string | ✓ | | +| [name](variables.tf#L190) | Cluster name. | string | ✓ | | +| [project_id](variables.tf#L226) | Cluster project ID. | string | ✓ | | +| [vpc_config](variables.tf#L242) | VPC-level configuration. | object({…}) | ✓ | | +| [backup_configs](variables.tf#L17) | Configuration for Backup for GKE. | object({…}) | | {} | +| [deletion_protection](variables.tf#L38) | Whether or not to allow Terraform to destroy the cluster. Unless this field is set to false in Terraform state, a terraform destroy or terraform apply that would delete the cluster will fail. | bool | | true | +| [description](variables.tf#L45) | Cluster description. | string | | null | +| [enable_addons](variables.tf#L51) | Addons enabled in the cluster (true means enabled). | object({…}) | | {} | +| [enable_features](variables.tf#L65) | Enable cluster-level features. Certain features allow configuration. | object({…}) | | {} | +| [issue_client_certificate](variables.tf#L101) | Enable issuing client certificate. | bool | | false | +| [labels](variables.tf#L107) | Cluster resource labels. | map(string) | | null | +| [logging_config](variables.tf#L118) | Logging configuration. | object({…}) | | {} | +| [maintenance_config](variables.tf#L129) | Maintenance window configuration. | object({…}) | | {…} | +| [min_master_version](variables.tf#L152) | Minimum version of the master, defaults to the version of the most recent official release. | string | | null | +| [monitoring_config](variables.tf#L158) | Monitoring configuration. System metrics collection cannot be disabled. Control plane metrics are optional. Kube state metrics are optional. Google Cloud Managed Service for Prometheus is enabled by default. | object({…}) | | {} | +| [node_config](variables.tf#L195) | Configuration for nodes and nodepools. | object({…}) | | {} | +| [node_locations](variables.tf#L205) | Zones in which the cluster's nodes are located. | list(string) | | [] | +| [private_cluster_config](variables.tf#L212) | Private cluster configuration. | object({…}) | | null | +| [release_channel](variables.tf#L231) | Release channel for GKE upgrades. Clusters created in the Autopilot mode must use a release channel. Choose between \"RAPID\", \"REGULAR\", and \"STABLE\". | string | | "REGULAR" | ## Outputs diff --git a/modules/gke-cluster-autopilot/main.tf b/modules/gke-cluster-autopilot/main.tf index 825c8739e5..6824d22309 100644 --- a/modules/gke-cluster-autopilot/main.tf +++ b/modules/gke-cluster-autopilot/main.tf @@ -322,6 +322,7 @@ resource "google_gke_backup_backup_plan" "backup_plan" { cluster = google_container_cluster.cluster.id location = each.value.region project = var.project_id + labels = each.value.labels retention_policy { backup_delete_lock_days = try(each.value.retention_policy_delete_lock_days) backup_retain_days = try(each.value.retention_policy_days) diff --git a/modules/gke-cluster-autopilot/variables.tf b/modules/gke-cluster-autopilot/variables.tf index 570899274e..a31596a6e6 100644 --- a/modules/gke-cluster-autopilot/variables.tf +++ b/modules/gke-cluster-autopilot/variables.tf @@ -22,6 +22,7 @@ variable "backup_configs" { encryption_key = optional(string) include_secrets = optional(bool, true) include_volume_data = optional(bool, true) + labels = optional(map(string)) namespaces = optional(list(string)) region = string schedule = string diff --git a/modules/gke-cluster-standard/README.md b/modules/gke-cluster-standard/README.md index c53017267f..dd8ad0f444 100644 --- a/modules/gke-cluster-standard/README.md +++ b/modules/gke-cluster-standard/README.md @@ -310,28 +310,28 @@ module "cluster-1" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [location](variables.tf#L234) | Cluster zone or region. | string | ✓ | | -| [name](variables.tf#L369) | Cluster name. | string | ✓ | | -| [project_id](variables.tf#L405) | Cluster project id. | string | ✓ | | -| [vpc_config](variables.tf#L416) | VPC-level configuration. | object({…}) | ✓ | | -| [backup_configs](variables.tf#L17) | Configuration for Backup for GKE. | object({…}) | | {} | -| [cluster_autoscaling](variables.tf#L38) | Enable and configure limits for Node Auto-Provisioning with Cluster Autoscaler. | object({…}) | | null | -| [default_nodepool](variables.tf#L117) | Enable default nodepool. | object({…}) | | {} | -| [deletion_protection](variables.tf#L135) | Whether or not to allow Terraform to destroy the cluster. Unless this field is set to false in Terraform state, a terraform destroy or terraform apply that would delete the cluster will fail. | bool | | true | -| [description](variables.tf#L142) | Cluster description. | string | | null | -| [enable_addons](variables.tf#L148) | Addons enabled in the cluster (true means enabled). | object({…}) | | {…} | -| [enable_features](variables.tf#L172) | Enable cluster-level features. Certain features allow configuration. | object({…}) | | {…} | -| [issue_client_certificate](variables.tf#L222) | Enable issuing client certificate. | bool | | false | -| [labels](variables.tf#L228) | Cluster resource labels. | map(string) | | null | -| [logging_config](variables.tf#L239) | Logging configuration. | object({…}) | | {} | -| [maintenance_config](variables.tf#L260) | Maintenance window configuration. | object({…}) | | {…} | -| [max_pods_per_node](variables.tf#L283) | Maximum number of pods per node in this cluster. | number | | 110 | -| [min_master_version](variables.tf#L289) | Minimum version of the master, defaults to the version of the most recent official release. | string | | null | -| [monitoring_config](variables.tf#L295) | Monitoring configuration. Google Cloud Managed Service for Prometheus is enabled by default. | object({…}) | | {} | -| [node_config](variables.tf#L374) | Node-level configuration. | object({…}) | | {} | -| [node_locations](variables.tf#L384) | Zones in which the cluster's nodes are located. | list(string) | | [] | -| [private_cluster_config](variables.tf#L391) | Private cluster configuration. | object({…}) | | null | -| [release_channel](variables.tf#L410) | Release channel for GKE upgrades. | string | | null | +| [location](variables.tf#L235) | Cluster zone or region. | string | ✓ | | +| [name](variables.tf#L370) | Cluster name. | string | ✓ | | +| [project_id](variables.tf#L406) | Cluster project id. | string | ✓ | | +| [vpc_config](variables.tf#L417) | VPC-level configuration. | object({…}) | ✓ | | +| [backup_configs](variables.tf#L17) | Configuration for Backup for GKE. | object({…}) | | {} | +| [cluster_autoscaling](variables.tf#L39) | Enable and configure limits for Node Auto-Provisioning with Cluster Autoscaler. | object({…}) | | null | +| [default_nodepool](variables.tf#L118) | Enable default nodepool. | object({…}) | | {} | +| [deletion_protection](variables.tf#L136) | Whether or not to allow Terraform to destroy the cluster. Unless this field is set to false in Terraform state, a terraform destroy or terraform apply that would delete the cluster will fail. | bool | | true | +| [description](variables.tf#L143) | Cluster description. | string | | null | +| [enable_addons](variables.tf#L149) | Addons enabled in the cluster (true means enabled). | object({…}) | | {…} | +| [enable_features](variables.tf#L173) | Enable cluster-level features. Certain features allow configuration. | object({…}) | | {…} | +| [issue_client_certificate](variables.tf#L223) | Enable issuing client certificate. | bool | | false | +| [labels](variables.tf#L229) | Cluster resource labels. | map(string) | | null | +| [logging_config](variables.tf#L240) | Logging configuration. | object({…}) | | {} | +| [maintenance_config](variables.tf#L261) | Maintenance window configuration. | object({…}) | | {…} | +| [max_pods_per_node](variables.tf#L284) | Maximum number of pods per node in this cluster. | number | | 110 | +| [min_master_version](variables.tf#L290) | Minimum version of the master, defaults to the version of the most recent official release. | string | | null | +| [monitoring_config](variables.tf#L296) | Monitoring configuration. Google Cloud Managed Service for Prometheus is enabled by default. | object({…}) | | {} | +| [node_config](variables.tf#L375) | Node-level configuration. | object({…}) | | {} | +| [node_locations](variables.tf#L385) | Zones in which the cluster's nodes are located. | list(string) | | [] | +| [private_cluster_config](variables.tf#L392) | Private cluster configuration. | object({…}) | | null | +| [release_channel](variables.tf#L411) | Release channel for GKE upgrades. | string | | null | ## Outputs diff --git a/modules/gke-cluster-standard/main.tf b/modules/gke-cluster-standard/main.tf index 99db49f80d..bc743fc5ce 100644 --- a/modules/gke-cluster-standard/main.tf +++ b/modules/gke-cluster-standard/main.tf @@ -511,6 +511,7 @@ resource "google_gke_backup_backup_plan" "backup_plan" { cluster = google_container_cluster.cluster.id location = each.value.region project = var.project_id + labels = each.value.labels retention_policy { backup_delete_lock_days = try(each.value.retention_policy_delete_lock_days) backup_retain_days = try(each.value.retention_policy_days) diff --git a/modules/gke-cluster-standard/variables.tf b/modules/gke-cluster-standard/variables.tf index 5580320f8f..2436b467cd 100644 --- a/modules/gke-cluster-standard/variables.tf +++ b/modules/gke-cluster-standard/variables.tf @@ -24,6 +24,7 @@ variable "backup_configs" { encryption_key = optional(string) include_secrets = optional(bool, true) include_volume_data = optional(bool, true) + labels = optional(map(string)) namespaces = optional(list(string)) schedule = optional(string) retention_policy_days = optional(number) From dccf5735c58f26fb08814eff7845ebab1297ac30 Mon Sep 17 00:00:00 2001 From: simonebruzzechesse <60114646+simonebruzzechesse@users.noreply.github.com> Date: Thu, 2 May 2024 08:09:10 +0200 Subject: [PATCH 13/31] fis issues with private workstation-cluster module and persistent_directories (#2247) Co-authored-by: Ludovico Magnocavallo --- modules/workstation-cluster/README.md | 3 +++ modules/workstation-cluster/main.tf | 21 ++++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/modules/workstation-cluster/README.md b/modules/workstation-cluster/README.md index cf45451479..05a0acdea2 100644 --- a/modules/workstation-cluster/README.md +++ b/modules/workstation-cluster/README.md @@ -59,6 +59,9 @@ module "workstation-cluster" { } workstation_configs = { my-workstation-config = { + gce_instance = { + disable_public_ip_addresses = true + } workstations = { my-workstation = { labels = { diff --git a/modules/workstation-cluster/main.tf b/modules/workstation-cluster/main.tf index 07399df4c8..b26a9fe579 100644 --- a/modules/workstation-cluster/main.tf +++ b/modules/workstation-cluster/main.tf @@ -70,8 +70,8 @@ resource "google_workstations_workstation_config" "configs" { pool_size = each.value.gce_instance.pool_size boot_disk_size_gb = each.value.gce_instance.boot_disk_size_gb tags = each.value.gce_instance.tags - disable_public_ip_addresses = each.value.disable_public_ip_addresses - enable_nested_virtualization = each.value.enable_nested_virtualization + disable_public_ip_addresses = each.value.gce_instance.disable_public_ip_addresses + enable_nested_virtualization = each.value.gce_instance.enable_nested_virtualization dynamic "shielded_instance_config" { for_each = each.value.gce_instance.shielded_instance_config == null ? [] : [""] content { @@ -81,7 +81,7 @@ resource "google_workstations_workstation_config" "configs" { } } dynamic "confidential_instance_config" { - for_each = each.value.gce_instance.enable_confidential_compute ? [] : [""] + for_each = each.value.gce_instance.enable_confidential_compute ? [""] : [] content { enable_confidential_compute = true } @@ -114,6 +114,21 @@ resource "google_workstations_workstation_config" "configs" { kms_key_service_account = each.value.encryption_key.kms_key_service_account } } + dynamic "persistent_directories" { + for_each = each.value.persistent_directories + content { + mount_path = persistent_directories.value.mount_path + dynamic "gce_pd" { + for_each = persistent_directories.value.gce_pd == null ? [] : [""] + content { + size_gb = persistent_directories.value.gce_pd.size_gb + fs_type = persistent_directories.value.gce_pd.fs_type + disk_type = persistent_directories.value.gce_pd.disk_type + reclaim_policy = persistent_directories.value.gce_pd.reclaim_policy + } + } + } + } } resource "google_workstations_workstation" "workstations" { From 94c32c1d71e96d8332b8e74f71045aa4e4b5e0a6 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 2 May 2024 08:56:26 +0200 Subject: [PATCH 14/31] Misc FAST fixes (#2253) * Misc FAST fixes * Fix readme * Fix FAST nva bgp tests --- .../0-bootstrap-tenant/README.md | 2 +- .../0-bootstrap-tenant/variables.tf | 4 ++-- fast/stages/0-bootstrap/README.md | 18 +++++++++++++-- fast/stages/0-bootstrap/variables.tf | 2 +- fast/stages/1-resman/README.md | 22 +++++++++---------- fast/stages/1-resman/outputs.tf | 2 +- fast/stages/1-resman/variables.tf | 17 +++++--------- .../data/hierarchical-ingress-rules.yaml | 4 ++-- .../data/hierarchical-ingress-rules.yaml | 4 ++-- .../data/hierarchical-ingress-rules.yaml | 4 ++-- .../data/hierarchical-ingress-rules.yaml | 4 ++-- .../data/hierarchical-ingress-rules.yaml | 4 ++-- tests/fast/stages/s0_bootstrap/checklist.yaml | 22 +++++++++---------- tests/fast/stages/s0_bootstrap/simple.yaml | 2 +- tests/fast/stages/s1_resman/checklist.tfvars | 2 +- tests/fast/stages/s1_resman/simple.tfvars | 2 +- .../s2_networking_a_peering/simple.tfvars | 2 +- .../stages/s2_networking_b_vpn/simple.tfvars | 2 +- .../stages/s2_networking_c_nva/simple.tfvars | 2 +- .../simple.tfvars | 2 +- .../s2_networking_e_nva_bgp/simple.tfvars | 2 +- .../s2_networking_e_nva_bgp/simple.yaml | 3 ++- .../s0_bootstrap_tenant/simple.tfvars | 2 +- .../s1_resman_tenant/simple.tfvars | 2 +- 24 files changed, 71 insertions(+), 61 deletions(-) diff --git a/fast/stages-multitenant/0-bootstrap-tenant/README.md b/fast/stages-multitenant/0-bootstrap-tenant/README.md index 9e3a74d84a..a1b3ba367b 100644 --- a/fast/stages-multitenant/0-bootstrap-tenant/README.md +++ b/fast/stages-multitenant/0-bootstrap-tenant/README.md @@ -208,7 +208,7 @@ This configuration is possible but unsupported and only exists for development p | [custom_roles](variables.tf#L95) | Custom roles defined at the organization level, in key => id format. | object({…}) | | null | 0-bootstrap | | [fast_features](variables.tf#L105) | Selective control for top-level FAST features. | object({…}) | | {} | 0-bootstrap | | [federated_identity_providers](variables.tf#L119) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | map(object({…})) | | {} | | -| [groups](variables.tf#L133) | Group names or IAM-format principals to grant organization-level permissions. If just the name is provided, the 'group:' principal and organization domain are interpolated. | object({…}) | | {} | 0-bootstrap | +| [groups](variables.tf#L133) | Group names or IAM-format principals to grant organization-level permissions. If just the name is provided, the 'group:' principal and organization domain are interpolated. | object({…}) | | {} | 0-bootstrap | | [iam](variables.tf#L146) | Tenant-level custom IAM settings in role => [principal] format. | map(list(string)) | | {} | | | [iam_bindings_additive](variables.tf#L152) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | | | [iam_by_principals](variables.tf#L167) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable. | map(list(string)) | | {} | | diff --git a/fast/stages-multitenant/0-bootstrap-tenant/variables.tf b/fast/stages-multitenant/0-bootstrap-tenant/variables.tf index 74daa0a9f1..5fa964beee 100644 --- a/fast/stages-multitenant/0-bootstrap-tenant/variables.tf +++ b/fast/stages-multitenant/0-bootstrap-tenant/variables.tf @@ -1,5 +1,5 @@ /** - * Copyright 2023 Google LLC + * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -136,7 +136,7 @@ variable "groups" { description = "Group names or IAM-format principals to grant organization-level permissions. If just the name is provided, the 'group:' principal and organization domain are interpolated." type = object({ gcp-devops = optional(string, "gcp-devops") - gcp-network-admins = optional(string, "gcp-network-admins") + gcp-network-admins = optional(string, "gcp-vpc-network-admins") gcp-security-admins = optional(string, "gcp-security-admins") }) nullable = false diff --git a/fast/stages/0-bootstrap/README.md b/fast/stages/0-bootstrap/README.md index ee5c7f52e7..43060232fe 100644 --- a/fast/stages/0-bootstrap/README.md +++ b/fast/stages/0-bootstrap/README.md @@ -39,6 +39,7 @@ Use the following diagram as a simple high level reference for the following sec - [Log sinks and log destinations](#log-sinks-and-log-destinations) - [Names and naming convention](#names-and-naming-convention) - [Workload Identity Federation](#workload-identity-federation) + - [Project folders](#project-folders) - [CI/CD repositories](#cicd-repositories) - [Toggling features](#toggling-features) - [Files](#files) @@ -533,6 +534,18 @@ workload_identity_providers = { } ``` +### Project folders + +By default this stage creates all its projects directly under the orgaization node. If desired, projects can be moved under a folder using the `project_parent_ids` variable. + +```tfvars +project_parent_ids = { + automation = "folders/1234567890" + billing = "folders/9876543210" + logging = "folders/1234567890" +} +``` + ### CI/CD repositories FAST is designed to directly support running in automated workflows from separate repositories for each stage. The `cicd_repositories` variable allows you to configure impersonation from external repositories leveraging Workload identity Federation, and pre-configures a FAST workflow file that can be used to validate and apply the code in each repository. @@ -595,9 +608,10 @@ The remaining configuration is manual, as it regards the repositories themselves Some FAST features can be enabled or disabled using the `fast_features` variables. While this variable is not directly used in the bootstrap stage, it can instruct the following stages to create certain resources only if needed. -The `fast_features` variable consists of 4 toggles: +The `fast_features` variable consists of 6 toggles: - **`data_platform`** controls the creation of required resources (folders, service accounts, buckets, IAM bindings) to deploy the [3-data-platform](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/tree/master/fast/stages/3-data-platform) stage +- **`gcve`** controls the creation of required resources (folders, service accounts, buckets, IAM bindings) to deploy the [3-gcve](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/tree/master/fast/stages/3-gcve) stage - **`gke`** controls the creation of required resources (folders, service accounts, buckets, IAM bindings) to deploy the [3-gke-multitenant](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/tree/master/fast/stages/3-gke-multitenant) stage - **`project_factory`** controls the creation of required resources (folders, service accounts, buckets, IAM bindings) to deploy the [3-project-factory](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/tree/master/fast/stages/3-project-factory) stage - **`sandbox`** controls the creation of a "Sandbox" top level folder with relaxed policies, intended for sandbox environments where users can experiment @@ -636,7 +650,7 @@ The `fast_features` variable consists of 4 toggles: | [essential_contacts](variables.tf#L86) | Email used for essential contacts, unset if null. | string | | null | | | [factories_config](variables.tf#L92) | Configuration for the resource factories or external data. | object({…}) | | {} | | | [fast_features](variables.tf#L104) | Selective control for top-level FAST features. | object({…}) | | {} | | -| [groups](variables.tf#L118) | Group names or IAM-format principals to grant organization-level permissions. If just the name is provided, the 'group:' principal and organization domain are interpolated. | object({…}) | | {} | | +| [groups](variables.tf#L118) | Group names or IAM-format principals to grant organization-level permissions. If just the name is provided, the 'group:' principal and organization domain are interpolated. | object({…}) | | {} | | | [iam](variables.tf#L134) | Organization-level custom IAM settings in role => [principal] format. | map(list(string)) | | {} | | | [iam_bindings_additive](variables.tf#L141) | Organization-level custom additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | | | [iam_by_principals](variables.tf#L156) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable. | map(list(string)) | | {} | | diff --git a/fast/stages/0-bootstrap/variables.tf b/fast/stages/0-bootstrap/variables.tf index b769f1088f..26ec513ac9 100644 --- a/fast/stages/0-bootstrap/variables.tf +++ b/fast/stages/0-bootstrap/variables.tf @@ -121,7 +121,7 @@ variable "groups" { type = object({ gcp-billing-admins = optional(string, "gcp-billing-admins") gcp-devops = optional(string, "gcp-devops") - gcp-network-admins = optional(string, "gcp-network-admins") + gcp-network-admins = optional(string, "gcp-vpc-network-admins") gcp-organization-admins = optional(string, "gcp-organization-admins") gcp-security-admins = optional(string, "gcp-security-admins") # aliased to gcp-devops as the checklist does not create it diff --git a/fast/stages/1-resman/README.md b/fast/stages/1-resman/README.md index 6b7836fd8b..5ac024870b 100644 --- a/fast/stages/1-resman/README.md +++ b/fast/stages/1-resman/README.md @@ -358,21 +358,21 @@ Due to its simplicity, this stage lends itself easily to customizations: adding |---|---|:---:|:---:|:---:|:---:| | [automation](variables.tf#L20) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-bootstrap | | [billing_account](variables.tf#L42) | Billing account id. If billing account is not part of the same org set `is_org_level` to `false`. To disable handling of billing IAM roles set `no_iam` to `true`. | object({…}) | ✓ | | 0-bootstrap | -| [organization](variables.tf#L232) | Organization details. | object({…}) | ✓ | | 0-bootstrap | -| [prefix](variables.tf#L248) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | +| [organization](variables.tf#L227) | Organization details. | object({…}) | ✓ | | 0-bootstrap | +| [prefix](variables.tf#L243) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | | [cicd_repositories](variables.tf#L53) | CI/CD repository configuration. Identity providers reference keys in the `automation.federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…}) | | null | | | [custom_roles](variables.tf#L147) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 0-bootstrap | | [factories_config](variables.tf#L159) | Configuration for the resource factories or external data. | object({…}) | | {} | | | [fast_features](variables.tf#L168) | Selective control for top-level FAST features. | object({…}) | | {} | 0-0-bootstrap | | [folder_iam](variables.tf#L183) | Authoritative IAM for top-level folders. | object({…}) | | {} | | -| [groups](variables.tf#L199) | Group names or IAM-format principals to grant organization-level permissions. If just the name is provided, the 'group:' principal and organization domain are interpolated. | object({…}) | | {} | 0-bootstrap | -| [locations](variables.tf#L214) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {…} | 0-bootstrap | -| [outputs_location](variables.tf#L242) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | -| [tag_names](variables.tf#L259) | Customized names for resource management tags. | object({…}) | | {} | | -| [tags](variables.tf#L274) | Custom secure tags by key name. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} | | -| [team_folders](variables.tf#L295) | Team folders to be created. Format is described in a code comment. | map(object({…})) | | null | | -| [tenants](variables.tf#L311) | Lightweight tenant definitions. | map(object({…})) | | {} | | -| [tenants_config](variables.tf#L327) | Lightweight tenants shared configuration. Roles will be assigned to tenant admin group and service accounts. | object({…}) | | {} | | +| [groups](variables.tf#L199) | Group names or IAM-format principals to grant organization-level permissions. If just the name is provided, the 'group:' principal and organization domain are interpolated. | object({…}) | | {} | 0-bootstrap | +| [locations](variables.tf#L214) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {} | 0-bootstrap | +| [outputs_location](variables.tf#L237) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | +| [tag_names](variables.tf#L254) | Customized names for resource management tags. | object({…}) | | {} | | +| [tags](variables.tf#L269) | Custom secure tags by key name. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} | | +| [team_folders](variables.tf#L290) | Team folders to be created. Format is described in a code comment. | map(object({…})) | | null | | +| [tenants](variables.tf#L306) | Lightweight tenant definitions. | map(object({…})) | | {} | | +| [tenants_config](variables.tf#L322) | Lightweight tenants shared configuration. Roles will be assigned to tenant admin group and service accounts. | object({…}) | | {} | | ## Outputs @@ -380,7 +380,7 @@ Due to its simplicity, this stage lends itself easily to customizations: adding |---|---|:---:|---| | [cicd_repositories](outputs.tf#L391) | WIF configuration for CI/CD repositories. | | | | [dataplatform](outputs.tf#L405) | Data for the Data Platform stage. | | | -| [gcve](outputs.tf#L421) | Data for the GCVE stage. | | 03-gke-multitenant | +| [gcve](outputs.tf#L421) | Data for the GCVE stage. | | 03-gcve | | [gke_multitenant](outputs.tf#L442) | Data for the GKE multitenant stage. | | 03-gke-multitenant | | [networking](outputs.tf#L463) | Data for the networking stage. | | | | [project_factories](outputs.tf#L472) | Data for the project factories stage. | | | diff --git a/fast/stages/1-resman/outputs.tf b/fast/stages/1-resman/outputs.tf index 31e9590203..de411b9115 100644 --- a/fast/stages/1-resman/outputs.tf +++ b/fast/stages/1-resman/outputs.tf @@ -419,7 +419,7 @@ output "dataplatform" { } output "gcve" { - # tfdoc:output:consumers 03-gke-multitenant + # tfdoc:output:consumers 03-gcve description = "Data for the GCVE stage." value = ( var.fast_features.gcve diff --git a/fast/stages/1-resman/variables.tf b/fast/stages/1-resman/variables.tf index 8417466730..3256b9bb74 100644 --- a/fast/stages/1-resman/variables.tf +++ b/fast/stages/1-resman/variables.tf @@ -203,7 +203,7 @@ variable "groups" { type = object({ gcp-billing-admins = optional(string, "gcp-billing-admins") gcp-devops = optional(string, "gcp-devops") - gcp-network-admins = optional(string, "gcp-network-admins") + gcp-network-admins = optional(string, "gcp-vpc-network-admins") gcp-organization-admins = optional(string, "gcp-organization-admins") gcp-security-admins = optional(string, "gcp-security-admins") }) @@ -215,18 +215,13 @@ variable "locations" { # tfdoc:variable:source 0-bootstrap description = "Optional locations for GCS, BigQuery, and logging buckets created here." type = object({ - bq = string - gcs = string - logging = string - pubsub = list(string) + bq = optional(string, "EU") + gcs = optional(string, "EU") + logging = optional(string, "global") + pubsub = optional(list(string), []) }) - default = { - bq = "EU" - gcs = "EU" - logging = "global" - pubsub = [] - } nullable = false + default = {} } variable "organization" { diff --git a/fast/stages/2-networking-a-peering/data/hierarchical-ingress-rules.yaml b/fast/stages/2-networking-a-peering/data/hierarchical-ingress-rules.yaml index 93d42fbee8..e444dd3f0b 100644 --- a/fast/stages/2-networking-a-peering/data/hierarchical-ingress-rules.yaml +++ b/fast/stages/2-networking-a-peering/data/hierarchical-ingress-rules.yaml @@ -11,14 +11,14 @@ # - rfc1918 allow-healthchecks: - description: Enable HTTP and HTTPS healthchecks + description: Enable SSH, HTTP and HTTPS healthchecks priority: 1001 match: source_ranges: - healthchecks layer4_configs: - protocol: tcp - ports: ["80", "443"] + ports: ["22", "80", "443"] allow-ssh-from-iap: description: Enable SSH from IAP diff --git a/fast/stages/2-networking-b-vpn/data/hierarchical-ingress-rules.yaml b/fast/stages/2-networking-b-vpn/data/hierarchical-ingress-rules.yaml index 0504f376c8..817be2e99d 100644 --- a/fast/stages/2-networking-b-vpn/data/hierarchical-ingress-rules.yaml +++ b/fast/stages/2-networking-b-vpn/data/hierarchical-ingress-rules.yaml @@ -11,14 +11,14 @@ # - rfc1918 allow-healthchecks: - description: Enable HTTP and HTTPS healthchecks + description: Enable SSH, HTTP and HTTPS healthchecks priority: 1001 match: source_ranges: - healthchecks layer4_configs: - protocol: tcp - ports: ["80", "443"] + ports: ["22", "80", "443"] allow-ssh-from-iap: description: Enable SSH from IAP diff --git a/fast/stages/2-networking-c-nva/data/hierarchical-ingress-rules.yaml b/fast/stages/2-networking-c-nva/data/hierarchical-ingress-rules.yaml index 0504f376c8..817be2e99d 100644 --- a/fast/stages/2-networking-c-nva/data/hierarchical-ingress-rules.yaml +++ b/fast/stages/2-networking-c-nva/data/hierarchical-ingress-rules.yaml @@ -11,14 +11,14 @@ # - rfc1918 allow-healthchecks: - description: Enable HTTP and HTTPS healthchecks + description: Enable SSH, HTTP and HTTPS healthchecks priority: 1001 match: source_ranges: - healthchecks layer4_configs: - protocol: tcp - ports: ["80", "443"] + ports: ["22", "80", "443"] allow-ssh-from-iap: description: Enable SSH from IAP diff --git a/fast/stages/2-networking-d-separate-envs/data/hierarchical-ingress-rules.yaml b/fast/stages/2-networking-d-separate-envs/data/hierarchical-ingress-rules.yaml index 0504f376c8..817be2e99d 100644 --- a/fast/stages/2-networking-d-separate-envs/data/hierarchical-ingress-rules.yaml +++ b/fast/stages/2-networking-d-separate-envs/data/hierarchical-ingress-rules.yaml @@ -11,14 +11,14 @@ # - rfc1918 allow-healthchecks: - description: Enable HTTP and HTTPS healthchecks + description: Enable SSH, HTTP and HTTPS healthchecks priority: 1001 match: source_ranges: - healthchecks layer4_configs: - protocol: tcp - ports: ["80", "443"] + ports: ["22", "80", "443"] allow-ssh-from-iap: description: Enable SSH from IAP diff --git a/fast/stages/2-networking-e-nva-bgp/data/hierarchical-ingress-rules.yaml b/fast/stages/2-networking-e-nva-bgp/data/hierarchical-ingress-rules.yaml index 0504f376c8..817be2e99d 100644 --- a/fast/stages/2-networking-e-nva-bgp/data/hierarchical-ingress-rules.yaml +++ b/fast/stages/2-networking-e-nva-bgp/data/hierarchical-ingress-rules.yaml @@ -11,14 +11,14 @@ # - rfc1918 allow-healthchecks: - description: Enable HTTP and HTTPS healthchecks + description: Enable SSH, HTTP and HTTPS healthchecks priority: 1001 match: source_ranges: - healthchecks layer4_configs: - protocol: tcp - ports: ["80", "443"] + ports: ["22", "80", "443"] allow-ssh-from-iap: description: Enable SSH from IAP diff --git a/tests/fast/stages/s0_bootstrap/checklist.yaml b/tests/fast/stages/s0_bootstrap/checklist.yaml index 8c24a1fdfe..0b1d71d48f 100644 --- a/tests/fast/stages/s0_bootstrap/checklist.yaml +++ b/tests/fast/stages/s0_bootstrap/checklist.yaml @@ -55,9 +55,9 @@ values: module.organization.google_organization_iam_binding.authoritative["roles/cloudasset.owner"]: condition: [] members: - - group:gcp-network-admins@fast.example.com - group:gcp-organization-admins@fast.example.com - group:gcp-security-admins@fast.example.com + - group:gcp-vpc-network-admins@fast.example.com org_id: '123456789012' role: roles/cloudasset.owner module.organization.google_organization_iam_binding.authoritative["roles/cloudsupport.admin"]: @@ -70,8 +70,8 @@ values: condition: [] members: - group:gcp-devops@fast.example.com - - group:gcp-network-admins@fast.example.com - group:gcp-security-admins@fast.example.com + - group:gcp-vpc-network-admins@fast.example.com org_id: '123456789012' role: roles/cloudsupport.techSupportEditor module.organization.google_organization_iam_binding.authoritative["roles/compute.osAdminLogin"]: @@ -131,7 +131,7 @@ values: condition: [] members: - group:gcp-devops@fast.example.com - - group:gcp-network-admins@fast.example.com + - group:gcp-vpc-network-admins@fast.example.com - serviceAccount:fast-prod-bootstrap-0r@fast-prod-iac-core-0.iam.gserviceaccount.com - serviceAccount:fast-prod-resman-0r@fast-prod-iac-core-0.iam.gserviceaccount.com org_id: '123456789012' @@ -240,19 +240,19 @@ values: member: serviceAccount:fast-prod-resman-0r@fast-prod-iac-core-0.iam.gserviceaccount.com org_id: '123456789012' role: roles/billing.viewer - ? module.organization.google_organization_iam_member.bindings["roles/compute.networkAdmin-group:gcp-network-admins@fast.example.com"] + ? module.organization.google_organization_iam_member.bindings["roles/compute.networkAdmin-group:gcp-vpc-network-admins@fast.example.com"] : condition: [] - member: group:gcp-network-admins@fast.example.com + member: group:gcp-vpc-network-admins@fast.example.com org_id: '123456789012' role: roles/compute.networkAdmin - ? module.organization.google_organization_iam_member.bindings["roles/compute.orgFirewallPolicyAdmin-group:gcp-network-admins@fast.example.com"] + ? module.organization.google_organization_iam_member.bindings["roles/compute.orgFirewallPolicyAdmin-group:gcp-vpc-network-admins@fast.example.com"] : condition: [] - member: group:gcp-network-admins@fast.example.com + member: group:gcp-vpc-network-admins@fast.example.com org_id: '123456789012' role: roles/compute.orgFirewallPolicyAdmin - ? module.organization.google_organization_iam_member.bindings["roles/compute.securityAdmin-group:gcp-network-admins@fast.example.com"] + ? module.organization.google_organization_iam_member.bindings["roles/compute.securityAdmin-group:gcp-vpc-network-admins@fast.example.com"] : condition: [] - member: group:gcp-network-admins@fast.example.com + member: group:gcp-vpc-network-admins@fast.example.com org_id: '123456789012' role: roles/compute.securityAdmin ? module.organization.google_organization_iam_member.bindings["roles/compute.viewer-group:gcp-security-admins@fast.example.com"] @@ -260,9 +260,9 @@ values: member: group:gcp-security-admins@fast.example.com org_id: '123456789012' role: roles/compute.viewer - ? module.organization.google_organization_iam_member.bindings["roles/compute.xpnAdmin-group:gcp-network-admins@fast.example.com"] + ? module.organization.google_organization_iam_member.bindings["roles/compute.xpnAdmin-group:gcp-vpc-network-admins@fast.example.com"] : condition: [] - member: group:gcp-network-admins@fast.example.com + member: group:gcp-vpc-network-admins@fast.example.com org_id: '123456789012' role: roles/compute.xpnAdmin ? module.organization.google_organization_iam_member.bindings["roles/container.viewer-group:gcp-security-admins@fast.example.com"] diff --git a/tests/fast/stages/s0_bootstrap/simple.yaml b/tests/fast/stages/s0_bootstrap/simple.yaml index 69908ad358..bdc0ec68cb 100644 --- a/tests/fast/stages/s0_bootstrap/simple.yaml +++ b/tests/fast/stages/s0_bootstrap/simple.yaml @@ -16,9 +16,9 @@ values: module.organization.google_organization_iam_binding.authoritative["roles/cloudsupport.techSupportEditor"]: condition: [] members: - - group:gcp-network-admins@fast.example.com - group:gcp-security-admins@fast.example.com - group:gcp-support@example.com + - group:gcp-vpc-network-admins@fast.example.com org_id: '123456789012' role: roles/cloudsupport.techSupportEditor module.organization.google_organization_iam_binding.authoritative["roles/logging.viewer"]: diff --git a/tests/fast/stages/s1_resman/checklist.tfvars b/tests/fast/stages/s1_resman/checklist.tfvars index 88df6b629c..0e303043d1 100644 --- a/tests/fast/stages/s1_resman/checklist.tfvars +++ b/tests/fast/stages/s1_resman/checklist.tfvars @@ -24,7 +24,7 @@ factories_config = { groups = { gcp-billing-admins = "gcp-billing-admins", gcp-devops = "gcp-devops", - gcp-network-admins = "gcp-network-admins", + gcp-network-admins = "gcp-vpc-network-admins", gcp-organization-admins = "gcp-organization-admins", gcp-security-admins = "gcp-security-admins", gcp-support = "gcp-support" diff --git a/tests/fast/stages/s1_resman/simple.tfvars b/tests/fast/stages/s1_resman/simple.tfvars index 9cb8528817..8086201d88 100644 --- a/tests/fast/stages/s1_resman/simple.tfvars +++ b/tests/fast/stages/s1_resman/simple.tfvars @@ -21,7 +21,7 @@ custom_roles = { groups = { gcp-billing-admins = "gcp-billing-admins", gcp-devops = "gcp-devops", - gcp-network-admins = "gcp-network-admins", + gcp-network-admins = "gcp-vpc-network-admins", gcp-organization-admins = "gcp-organization-admins", gcp-security-admins = "gcp-security-admins", gcp-support = "gcp-support" diff --git a/tests/fast/stages/s2_networking_a_peering/simple.tfvars b/tests/fast/stages/s2_networking_a_peering/simple.tfvars index bdff7a433f..9a117062e3 100644 --- a/tests/fast/stages/s2_networking_a_peering/simple.tfvars +++ b/tests/fast/stages/s2_networking_a_peering/simple.tfvars @@ -19,7 +19,7 @@ folder_ids = { networking-prod = null } groups = { - gcp-network-admins = "gcp-network-admins" + gcp-network-admins = "gcp-vpc-network-admins" } service_accounts = { data-platform-dev = "string" diff --git a/tests/fast/stages/s2_networking_b_vpn/simple.tfvars b/tests/fast/stages/s2_networking_b_vpn/simple.tfvars index 24d3a8e039..b9c4ec9ddb 100644 --- a/tests/fast/stages/s2_networking_b_vpn/simple.tfvars +++ b/tests/fast/stages/s2_networking_b_vpn/simple.tfvars @@ -19,7 +19,7 @@ folder_ids = { networking-prod = null } groups = { - gcp-network-admins = "gcp-network-admins" + gcp-network-admins = "gcp-vpc-network-admins" } service_accounts = { data-platform-dev = "string" diff --git a/tests/fast/stages/s2_networking_c_nva/simple.tfvars b/tests/fast/stages/s2_networking_c_nva/simple.tfvars index fca8913f8a..c2a9cef0bd 100644 --- a/tests/fast/stages/s2_networking_c_nva/simple.tfvars +++ b/tests/fast/stages/s2_networking_c_nva/simple.tfvars @@ -19,7 +19,7 @@ folder_ids = { networking-prod = null } groups = { - gcp-network-admins = "gcp-network-admins" + gcp-network-admins = "gcp-vpc-network-admins" } service_accounts = { data-platform-dev = "string" diff --git a/tests/fast/stages/s2_networking_d_separate_envs/simple.tfvars b/tests/fast/stages/s2_networking_d_separate_envs/simple.tfvars index 071011dd5e..8522e2b2f0 100644 --- a/tests/fast/stages/s2_networking_d_separate_envs/simple.tfvars +++ b/tests/fast/stages/s2_networking_d_separate_envs/simple.tfvars @@ -20,7 +20,7 @@ folder_ids = { networking-prod = null } groups = { - gcp-network-admins = "gcp-network-admins" + gcp-network-admins = "gcp-vpc-network-admins" } service_accounts = { data-platform-dev = "string" diff --git a/tests/fast/stages/s2_networking_e_nva_bgp/simple.tfvars b/tests/fast/stages/s2_networking_e_nva_bgp/simple.tfvars index fca8913f8a..c2a9cef0bd 100644 --- a/tests/fast/stages/s2_networking_e_nva_bgp/simple.tfvars +++ b/tests/fast/stages/s2_networking_e_nva_bgp/simple.tfvars @@ -19,7 +19,7 @@ folder_ids = { networking-prod = null } groups = { - gcp-network-admins = "gcp-network-admins" + gcp-network-admins = "gcp-vpc-network-admins" } service_accounts = { data-platform-dev = "string" diff --git a/tests/fast/stages/s2_networking_e_nva_bgp/simple.yaml b/tests/fast/stages/s2_networking_e_nva_bgp/simple.yaml index f0594c4ca6..993910f7e7 100644 --- a/tests/fast/stages/s2_networking_e_nva_bgp/simple.yaml +++ b/tests/fast/stages/s2_networking_e_nva_bgp/simple.yaml @@ -740,7 +740,7 @@ values: timeouts: null ? module.firewall-policy-default.google_compute_firewall_policy_rule.hierarchical["ingress/allow-healthchecks"] : action: allow - description: Enable HTTP and HTTPS healthchecks + description: Enable SSH, HTTP and HTTPS healthchecks direction: INGRESS disabled: false enable_logging: null @@ -753,6 +753,7 @@ values: layer4_configs: - ip_protocol: tcp ports: + - "22" - "80" - "443" src_address_groups: null diff --git a/tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.tfvars b/tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.tfvars index 52ca76a3db..e3691e9ca5 100644 --- a/tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.tfvars +++ b/tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.tfvars @@ -16,7 +16,7 @@ custom_roles = { groups = { gcp-billing-admins = "gcp-billing-admins", gcp-devops = "gcp-devops", - gcp-network-admins = "gcp-network-admins", + gcp-network-admins = "gcp-vpc-network-admins", gcp-organization-admins = "gcp-organization-admins", gcp-security-admins = "gcp-security-admins", gcp-support = "gcp-support" diff --git a/tests/fast/stages_multitenant/s1_resman_tenant/simple.tfvars b/tests/fast/stages_multitenant/s1_resman_tenant/simple.tfvars index 33cf461989..9cc9d46c69 100644 --- a/tests/fast/stages_multitenant/s1_resman_tenant/simple.tfvars +++ b/tests/fast/stages_multitenant/s1_resman_tenant/simple.tfvars @@ -34,7 +34,7 @@ fast_features = { } groups = { gcp-devops = "gcp-devops", - gcp-network-admins = "gcp-network-admins", + gcp-network-admins = "gcp-vpc-network-admins", gcp-security-admins = "gcp-security-admins", } organization = { From 7aa6c7e059d5e46a8630259ae5bd1edeefaa3336 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 2 May 2024 22:11:33 +0200 Subject: [PATCH 15/31] Style fixes to FAST log sinks expressions --- fast/stages/0-bootstrap/variables.tf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fast/stages/0-bootstrap/variables.tf b/fast/stages/0-bootstrap/variables.tf index 26ec513ac9..09c0e08567 100644 --- a/fast/stages/0-bootstrap/variables.tf +++ b/fast/stages/0-bootstrap/variables.tf @@ -200,14 +200,14 @@ variable "log_sinks" { } vpc-sc = { filter = <<-FILTER - protoPayload.metadata.@type:"type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata" + protoPayload.metadata.@type="type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata" FILTER type = "logging" } workspace-audit-logs = { filter = <<-FILTER - log_id("cloudaudit.googleapis.com/data_access") - protoPayload.serviceName:"login.googleapis.com" + log_id("cloudaudit.googleapis.com/data_access") AND + protoPayload.serviceName="login.googleapis.com" FILTER type = "logging" } From c9503d5ac506f4bd4c83afdc57f99a97463bdb62 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Thu, 9 May 2024 15:09:54 +0200 Subject: [PATCH 16/31] Remove data source from folder module (#2260) * remove data source from folder module * fix fast tfdoc * fix locals type error * fix folder test * fix fast test --- fast/stages/0-bootstrap/README.md | 2 +- modules/folder/iam.tf | 6 +++--- modules/folder/logging.tf | 9 ++++----- modules/folder/main.tf | 15 +++++---------- modules/folder/organization-policies.tf | 4 ++-- modules/folder/outputs.tf | 6 +++--- modules/folder/tags.tf | 2 +- .../stages/s2_networking_e_nva_bgp/simple.yaml | 2 -- .../s0_bootstrap_tenant/simple.yaml | 4 ++-- .../test_plan_org_policies_modules.py | 8 ++++---- 10 files changed, 25 insertions(+), 33 deletions(-) diff --git a/fast/stages/0-bootstrap/README.md b/fast/stages/0-bootstrap/README.md index 43060232fe..5d456302f5 100644 --- a/fast/stages/0-bootstrap/README.md +++ b/fast/stages/0-bootstrap/README.md @@ -655,7 +655,7 @@ The `fast_features` variable consists of 6 toggles: | [iam_bindings_additive](variables.tf#L141) | Organization-level custom additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | | | [iam_by_principals](variables.tf#L156) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable. | map(list(string)) | | {} | | | [locations](variables.tf#L163) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {} | | -| [log_sinks](variables.tf#L177) | Org-level log sinks, in name => {type, filter} format. | map(object({…})) | | {…} | | +| [log_sinks](variables.tf#L177) | Org-level log sinks, in name => {type, filter} format. | map(object({…})) | | {…} | | | [org_policies_config](variables.tf#L224) | Organization policies customization. | object({…}) | | {} | | | [outputs_location](variables.tf#L250) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | | [project_parent_ids](variables.tf#L265) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the organization as parent. | object({…}) | | {} | | diff --git a/modules/folder/iam.tf b/modules/folder/iam.tf index ba852837cb..68d3e606b0 100644 --- a/modules/folder/iam.tf +++ b/modules/folder/iam.tf @@ -35,14 +35,14 @@ locals { resource "google_folder_iam_binding" "authoritative" { for_each = local.iam - folder = local.folder.name + folder = local.folder_id role = each.key members = each.value } resource "google_folder_iam_binding" "bindings" { for_each = var.iam_bindings - folder = local.folder.name + folder = local.folder_id role = each.value.role members = each.value.members dynamic "condition" { @@ -57,7 +57,7 @@ resource "google_folder_iam_binding" "bindings" { resource "google_folder_iam_member" "bindings" { for_each = var.iam_bindings_additive - folder = local.folder.name + folder = local.folder_id role = each.value.role member = each.value.member dynamic "condition" { diff --git a/modules/folder/logging.tf b/modules/folder/logging.tf index f6942baa4b..817c4d1e06 100644 --- a/modules/folder/logging.tf +++ b/modules/folder/logging.tf @@ -37,7 +37,7 @@ locals { resource "google_folder_iam_audit_config" "default" { for_each = var.logging_data_access - folder = local.folder.name + folder = local.folder_id service = each.key dynamic "audit_log_config" { for_each = each.value @@ -53,7 +53,7 @@ resource "google_logging_folder_sink" "sink" { for_each = local.logging_sinks name = each.key description = coalesce(each.value.description, "${each.key} (Terraform-managed).") - folder = local.folder.name + folder = local.folder_id destination = "${each.value.type}.googleapis.com/${each.value.destination}" filter = each.value.filter include_children = each.value.include_children @@ -108,10 +108,9 @@ resource "google_project_iam_member" "bucket-sinks-binding" { project = split("/", each.value.destination)[1] role = "roles/logging.bucketWriter" member = google_logging_folder_sink.sink[each.key].writer_identity - 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}" + 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}')" } } @@ -126,7 +125,7 @@ resource "google_project_iam_member" "project-sinks-binding" { resource "google_logging_folder_exclusion" "logging-exclusion" { for_each = var.logging_exclusions name = each.key - folder = local.folder.name + folder = local.folder_id description = "${each.key} (Terraform-managed)." filter = each.value } diff --git a/modules/folder/main.tf b/modules/folder/main.tf index 4335dbe515..8bb46a533c 100644 --- a/modules/folder/main.tf +++ b/modules/folder/main.tf @@ -15,18 +15,13 @@ */ locals { - folder = ( + folder_id = ( var.folder_create - ? try(google_folder.folder[0], null) - : try(data.google_folder.folder[0], null) + ? try(google_folder.folder[0].id, null) + : var.id ) } -data "google_folder" "folder" { - count = var.folder_create ? 0 : 1 - folder = var.id -} - resource "google_folder" "folder" { count = var.folder_create ? 1 : 0 display_name = var.name @@ -36,7 +31,7 @@ resource "google_folder" "folder" { resource "google_essential_contacts_contact" "contact" { provider = google-beta for_each = var.contacts - parent = local.folder.name + parent = local.folder_id email = each.key language_tag = "en" notification_category_subscriptions = each.value @@ -49,7 +44,7 @@ resource "google_essential_contacts_contact" "contact" { resource "google_compute_firewall_policy_association" "default" { count = var.firewall_policy == null ? 0 : 1 - attachment_target = local.folder.id + attachment_target = local.folder_id name = var.firewall_policy.name firewall_policy = var.firewall_policy.policy } diff --git a/modules/folder/organization-policies.tf b/modules/folder/organization-policies.tf index 38b69871c9..6de3081ce8 100644 --- a/modules/folder/organization-policies.tf +++ b/modules/folder/organization-policies.tf @@ -52,8 +52,8 @@ locals { org_policies = { for k, v in local._org_policies : k => merge(v, { - name = "${local.folder.name}/policies/${k}" - parent = local.folder.name + name = "${local.folder_id}/policies/${k}" + parent = local.folder_id is_boolean_policy = ( alltrue([for r in v.rules : r.allow == null && r.deny == null]) ) diff --git a/modules/folder/outputs.tf b/modules/folder/outputs.tf index aec28f7152..79ff37cb7d 100644 --- a/modules/folder/outputs.tf +++ b/modules/folder/outputs.tf @@ -16,12 +16,12 @@ output "folder" { description = "Folder resource." - value = local.folder + value = try(google_folder.folder[0], null) } output "id" { description = "Fully qualified folder id." - value = local.folder.name + value = local.folder_id depends_on = [ google_folder_iam_binding.authoritative, google_folder_iam_binding.bindings, @@ -32,7 +32,7 @@ output "id" { output "name" { description = "Folder name." - value = local.folder.display_name + value = try(google_folder.folder[0].display_name, null) } output "sink_writer_identities" { diff --git a/modules/folder/tags.tf b/modules/folder/tags.tf index 2cd2f2fd1a..323886ef50 100644 --- a/modules/folder/tags.tf +++ b/modules/folder/tags.tf @@ -16,6 +16,6 @@ resource "google_tags_tag_binding" "binding" { for_each = coalesce(var.tag_bindings, {}) - parent = "//cloudresourcemanager.googleapis.com/${local.folder.id}" + parent = "//cloudresourcemanager.googleapis.com/${local.folder_id}" tag_value = each.value } diff --git a/tests/fast/stages/s2_networking_e_nva_bgp/simple.yaml b/tests/fast/stages/s2_networking_e_nva_bgp/simple.yaml index 993910f7e7..d9b2d67c9c 100644 --- a/tests/fast/stages/s2_networking_e_nva_bgp/simple.yaml +++ b/tests/fast/stages/s2_networking_e_nva_bgp/simple.yaml @@ -1520,8 +1520,6 @@ values: log_config: - enable: false filter: ALL - max_ports_per_vm: 65536 - min_ports_per_vm: 64 name: ew1 nat_ip_allocate_option: AUTO_ONLY nat_ips: null diff --git a/tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.yaml b/tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.yaml index 3e84c00ef6..f6f0d3ea26 100644 --- a/tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.yaml +++ b/tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.yaml @@ -14,7 +14,7 @@ counts: google_bigquery_default_service_account: 2 - google_folder: 2 + google_folder: 1 google_folder_iam_binding: 5 google_organization_iam_member: 39 google_project: 2 @@ -30,4 +30,4 @@ counts: google_tags_tag_binding: 1 google_tags_tag_value: 1 modules: 19 - resources: 129 + resources: 128 diff --git a/tests/modules/organization/test_plan_org_policies_modules.py b/tests/modules/organization/test_plan_org_policies_modules.py index 632b45e726..f8839fc251 100644 --- a/tests/modules/organization/test_plan_org_policies_modules.py +++ b/tests/modules/organization/test_plan_org_policies_modules.py @@ -37,8 +37,8 @@ def test_policy_implementation(): '@@ -55,2 +55,2 @@\n', '- name = "projects/${local.project.project_id}/policies/${k}"\n', '- parent = "projects/${local.project.project_id}"\n', - '+ name = "${local.folder.name}/policies/${k}"\n', - '+ parent = local.folder.name\n', + '+ name = "${local.folder_id}/policies/${k}"\n', + '+ parent = local.folder_id\n', ] diff2 = difflib.unified_diff(lines['folder'], lines['organization'], 'folder', @@ -50,8 +50,8 @@ def test_policy_implementation(): '-# tfdoc:file:description Folder-level organization policies.\n', '+# tfdoc:file:description Organization-level organization policies.\n', '@@ -55,2 +55,2 @@\n', - '- name = "${local.folder.name}/policies/${k}"\n', - '- parent = local.folder.name\n', + '- name = "${local.folder_id}/policies/${k}"\n', + '- parent = local.folder_id\n', '+ name = "${var.organization_id}/policies/${k}"\n', '+ parent = var.organization_id\n', '@@ -113,0 +114,9 @@\n', From c58850c096df0a194dc1437512267f99e4ca2d9d Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 9 May 2024 15:24:41 +0200 Subject: [PATCH 17/31] Add Hybrid NAT support (#2261) * Updates to support hybid NAT * Fix readme * Fix variable order --- modules/net-cloudnat/README.md | 61 +++++++++++- modules/net-cloudnat/main.tf | 25 +++-- modules/net-cloudnat/variables.tf | 42 ++++++--- modules/net-vpc/README.md | 12 ++- modules/net-vpc/outputs.tf | 7 +- modules/net-vpc/subnets.tf | 20 +++- modules/net-vpc/variables.tf | 14 ++- .../s2_networking_e_nva_bgp/simple.yaml | 4 +- .../modules/net_cloudnat/examples/hybrid.yaml | 94 +++++++++++++++++++ 9 files changed, 247 insertions(+), 32 deletions(-) create mode 100644 tests/modules/net_cloudnat/examples/hybrid.yaml diff --git a/modules/net-cloudnat/README.md b/modules/net-cloudnat/README.md index 407bce3f08..34fb560660 100644 --- a/modules/net-cloudnat/README.md +++ b/modules/net-cloudnat/README.md @@ -6,6 +6,7 @@ Simple Cloud NAT management, with optional router creation. - [Basic Example](#basic-example) - [Subnetwork configuration](#subnetwork-configuration) - [Reserved IPs and custom rules](#reserved-ips-and-custom-rules) +- [Hybrid NAT](#hybrid-nat) - [Variables](#variables) - [Outputs](#outputs) @@ -102,6 +103,59 @@ module "nat" { } # tftest modules=2 resources=5 inventory=rules.yaml e2e ``` +## Hybrid NAT +```hcl +module "vpc1" { + source = "./fabric/modules/net-vpc" + project_id = var.project_id + name = "vpc1" + subnets = [ + { + ip_cidr_range = "10.0.0.0/24" + name = "vpc1-subnet" + region = var.region + } + ] + subnets_private_nat = [ + { + ip_cidr_range = "192.168.15.0/24" + name = "vpc1-nat" + region = var.region + } + ] +} + +module "vpc1-nat" { + source = "./fabric/modules/net-cloudnat" + project_id = var.project_id + region = var.region + name = "vpc1-nat" + type = "PRIVATE" + router_network = module.vpc1.id + config_source_subnetworks = { + all = false + subnetworks = [ + { + self_link = module.vpc1.subnet_ids["${var.region}/vpc1-subnet"] + } + ] + } + config_port_allocation = { + enable_endpoint_independent_mapping = false + enable_dynamic_port_allocation = true + } + rules = [ + { + description = "private nat" + match = "nexthop.is_hybrid" + source_ranges = [ + module.vpc1.subnets_private_nat["${var.region}/vpc1-nat"].id + ] + } + ] +} +# tftest modules=2 resources=7 inventory=hybrid.yaml +``` ## Variables @@ -111,15 +165,16 @@ module "nat" { | [project_id](variables.tf#L82) | Project where resources will be created. | string | ✓ | | | [region](variables.tf#L87) | Region where resources will be created. | string | ✓ | | | [addresses](variables.tf#L17) | Optional list of external address self links. | list(string) | | [] | -| [config_port_allocation](variables.tf#L23) | Configuration for how to assign ports to virtual machines. min_ports_per_vm and max_ports_per_vm have no effect unless enable_dynamic_port_allocation is set to 'true'. | object({…}) | | {} | +| [config_port_allocation](variables.tf#L23) | Configuration for how to assign ports to virtual machines. min_ports_per_vm and max_ports_per_vm have no effect unless enable_dynamic_port_allocation is set to 'true'. | object({…}) | | {} | | [config_source_subnetworks](variables.tf#L39) | Subnetwork configuration. | object({…}) | | {} | -| [config_timeouts](variables.tf#L58) | Timeout configurations. | object({…}) | | {} | +| [config_timeouts](variables.tf#L58) | Timeout configurations. | object({…}) | | {} | | [logging_filter](variables.tf#L71) | Enables logging if not null, value is one of 'ERRORS_ONLY', 'TRANSLATIONS_ONLY', 'ALL'. | string | | null | | [router_asn](variables.tf#L92) | Router ASN used for auto-created router. | number | | null | | [router_create](variables.tf#L98) | Create router. | bool | | true | | [router_name](variables.tf#L104) | Router name, leave blank if router will be created to use auto generated name. | string | | null | | [router_network](variables.tf#L110) | Name of the VPC used for auto-created router. | string | | null | -| [rules](variables.tf#L116) | List of rules associated with this NAT. | list(object({…})) | | [] | +| [rules](variables.tf#L116) | List of rules associated with this NAT. | list(object({…})) | | [] | +| [type](variables.tf#L136) | Whether this Cloud NAT is used for public or private IP translation. One of 'PUBLIC' or 'PRIVATE'. | string | | "PUBLIC" | ## Outputs diff --git a/modules/net-cloudnat/main.tf b/modules/net-cloudnat/main.tf index 7a537d8eee..bf5b7bc785 100644 --- a/modules/net-cloudnat/main.tf +++ b/modules/net-cloudnat/main.tf @@ -1,5 +1,5 @@ /** - * Copyright 2023 Google LLC + * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,13 +47,21 @@ resource "google_compute_router" "router" { } resource "google_compute_router_nat" "nat" { - project = var.project_id - region = var.region - name = var.name - router = local.router_name - nat_ips = var.addresses + provider = google-beta + project = var.project_id + region = var.region + name = var.name + type = var.type + router = local.router_name + nat_ips = var.addresses nat_ip_allocate_option = ( - length(var.addresses) > 0 ? "MANUAL_ONLY" : "AUTO_ONLY" + var.type == "PRIVATE" + ? null + : ( + length(var.addresses) > 0 + ? "MANUAL_ONLY" + : "AUTO_ONLY" + ) ) source_subnetwork_ip_ranges_to_nat = local.subnet_config icmp_idle_timeout_sec = var.config_timeouts.icmp @@ -114,7 +122,8 @@ resource "google_compute_router_nat" "nat" { description = rules.value.description match = rules.value.match action { - source_nat_active_ips = rules.value.source_ips + source_nat_active_ips = rules.value.source_ips + source_nat_active_ranges = rules.value.source_ranges } } } diff --git a/modules/net-cloudnat/variables.tf b/modules/net-cloudnat/variables.tf index 0ec05e4221..27484ee319 100644 --- a/modules/net-cloudnat/variables.tf +++ b/modules/net-cloudnat/variables.tf @@ -1,5 +1,5 @@ /** - * Copyright 2023 Google LLC + * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,8 +25,8 @@ variable "config_port_allocation" { type = object({ enable_endpoint_independent_mapping = optional(bool, true) enable_dynamic_port_allocation = optional(bool, false) - min_ports_per_vm = optional(number, 64) - max_ports_per_vm = optional(number, 65536) + min_ports_per_vm = optional(number) + max_ports_per_vm = optional(number) }) default = {} nullable = false @@ -58,11 +58,11 @@ output "foo" { variable "config_timeouts" { description = "Timeout configurations." type = object({ - icmp = optional(number, 30) - tcp_established = optional(number, 1200) - tcp_time_wait = optional(number, 120) - tcp_transitory = optional(number, 30) - udp = optional(number, 30) + icmp = optional(number) + tcp_established = optional(number) + tcp_time_wait = optional(number) + tcp_transitory = optional(number) + udp = optional(number) }) default = {} nullable = false @@ -116,10 +116,30 @@ variable "router_network" { variable "rules" { description = "List of rules associated with this NAT." type = list(object({ - description = optional(string), - match = string - source_ips = list(string) + description = optional(string) + match = string + source_ips = optional(list(string)) + source_ranges = optional(list(string)) })) default = [] nullable = false + validation { + condition = alltrue([ + for r in var.rules : + r.source_ips != null || r.source_ranges != null + ]) + + error_message = "All rules must specify either source_ips or source_ranges." + } +} + +variable "type" { + description = "Whether this Cloud NAT is used for public or private IP translation. One of 'PUBLIC' or 'PRIVATE'." + type = string + default = "PUBLIC" + nullable = false + validation { + condition = var.type == "PUBLIC" || var.type == "PRIVATE" + error_message = "Field type must be either PUBLIC or PRIVATE." + } } diff --git a/modules/net-vpc/README.md b/modules/net-vpc/README.md index feee775cc6..d29251cc96 100644 --- a/modules/net-vpc/README.md +++ b/modules/net-vpc/README.md @@ -662,9 +662,10 @@ module "vpc" { | [shared_vpc_host](variables.tf#L238) | Enable shared VPC for this project. | bool | | false | | [shared_vpc_service_projects](variables.tf#L244) | Shared VPC service projects to register with this host. | list(string) | | [] | | [subnets](variables.tf#L250) | Subnet configuration. | list(object({…})) | | [] | -| [subnets_proxy_only](variables.tf#L297) | List of proxy-only subnets for Regional HTTPS or Internal HTTPS load balancers. Note: Only one proxy-only subnet for each VPC network in each region can be active. | list(object({…})) | | [] | -| [subnets_psc](variables.tf#L331) | List of subnets for Private Service Connect service producers. | list(object({…})) | | [] | -| [vpc_create](variables.tf#L363) | Create VPC. When set to false, uses a data source to reference existing VPC. | bool | | true | +| [subnets_private_nat](variables.tf#L297) | List of private NAT subnets. | list(object({…})) | | [] | +| [subnets_proxy_only](variables.tf#L309) | List of proxy-only subnets for Regional HTTPS or Internal HTTPS load balancers. Note: Only one proxy-only subnet for each VPC network in each region can be active. | list(object({…})) | | [] | +| [subnets_psc](variables.tf#L343) | List of subnets for Private Service Connect service producers. | list(object({…})) | | [] | +| [vpc_create](variables.tf#L375) | Create VPC. When set to false, uses a data source to reference existing VPC. | bool | | true | ## Outputs @@ -684,6 +685,7 @@ module "vpc" { | [subnet_secondary_ranges](outputs.tf#L122) | Map of subnet secondary ranges keyed by name. | | | [subnet_self_links](outputs.tf#L133) | Map of subnet self links keyed by name. | | | [subnets](outputs.tf#L142) | Subnet resources. | | -| [subnets_proxy_only](outputs.tf#L151) | L7 ILB or L7 Regional LB subnet resources. | | -| [subnets_psc](outputs.tf#L156) | Private Service Connect subnet resources. | | +| [subnets_private_nat](outputs.tf#L151) | Private NAT subnet resources. | | +| [subnets_proxy_only](outputs.tf#L156) | L7 ILB or L7 Regional LB subnet resources. | | +| [subnets_psc](outputs.tf#L161) | Private Service Connect subnet resources. | | diff --git a/modules/net-vpc/outputs.tf b/modules/net-vpc/outputs.tf index 412e363a4a..4d143e829f 100644 --- a/modules/net-vpc/outputs.tf +++ b/modules/net-vpc/outputs.tf @@ -1,5 +1,5 @@ /** - * Copyright 2023 Google LLC + * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -148,6 +148,11 @@ output "subnets" { ] } +output "subnets_private_nat" { + description = "Private NAT subnet resources." + value = { for k, v in google_compute_subnetwork.private_nat : k => v } +} + output "subnets_proxy_only" { description = "L7 ILB or L7 Regional LB subnet resources." value = { for k, v in google_compute_subnetwork.proxy_only : k => v } diff --git a/modules/net-vpc/subnets.tf b/modules/net-vpc/subnets.tf index ca3541450c..09d4748d00 100644 --- a/modules/net-vpc/subnets.tf +++ b/modules/net-vpc/subnets.tf @@ -1,5 +1,5 @@ /** - * Copyright 2023 Google LLC + * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -124,6 +124,10 @@ locals { { for s in var.subnets_proxy_only : "${s.region}/${s.name}" => s }, { for k, v in local._factory_subnets : k => v if v._is_proxy_only }, ) + subnets_private_nat = merge( + { for s in var.subnets_private_nat : "${s.region}/${s.name}" => s }, + # { for k, v in local._factory_subnets : k => v if v._is_proxy_only }, + ) subnets_psc = merge( { for s in var.subnets_psc : "${s.region}/${s.name}" => s }, { for k, v in local._factory_subnets : k => v if v._is_psc } @@ -185,6 +189,20 @@ resource "google_compute_subnetwork" "proxy_only" { role = each.value.active ? "ACTIVE" : "BACKUP" } +resource "google_compute_subnetwork" "private_nat" { + for_each = local.subnets_private_nat + project = var.project_id + network = local.network.name + name = each.value.name + region = each.value.region + ip_cidr_range = each.value.ip_cidr_range + description = coalesce( + each.value.description, + "Terraform-managed private NAT subnet." + ) + purpose = "PRIVATE_NAT" +} + resource "google_compute_subnetwork" "psc" { for_each = local.subnets_psc project = var.project_id diff --git a/modules/net-vpc/variables.tf b/modules/net-vpc/variables.tf index d8948a2640..6fd011bec8 100644 --- a/modules/net-vpc/variables.tf +++ b/modules/net-vpc/variables.tf @@ -1,5 +1,5 @@ /** - * Copyright 2023 Google LLC + * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -294,6 +294,18 @@ variable "subnets" { nullable = false } +variable "subnets_private_nat" { + description = "List of private NAT subnets." + type = list(object({ + name = string + ip_cidr_range = string + region = string + description = optional(string) + })) + default = [] + nullable = false +} + variable "subnets_proxy_only" { description = "List of proxy-only subnets for Regional HTTPS or Internal HTTPS load balancers. Note: Only one proxy-only subnet for each VPC network in each region can be active." type = list(object({ diff --git a/tests/fast/stages/s2_networking_e_nva_bgp/simple.yaml b/tests/fast/stages/s2_networking_e_nva_bgp/simple.yaml index d9b2d67c9c..38a23789d5 100644 --- a/tests/fast/stages/s2_networking_e_nva_bgp/simple.yaml +++ b/tests/fast/stages/s2_networking_e_nva_bgp/simple.yaml @@ -1520,6 +1520,7 @@ values: log_config: - enable: false filter: ALL + max_ports_per_vm: null name: ew1 nat_ip_allocate_option: AUTO_ONLY nat_ips: null @@ -1551,8 +1552,7 @@ values: log_config: - enable: false filter: ALL - max_ports_per_vm: 65536 - min_ports_per_vm: 64 + max_ports_per_vm: null name: ew4 nat_ip_allocate_option: AUTO_ONLY nat_ips: null diff --git a/tests/modules/net_cloudnat/examples/hybrid.yaml b/tests/modules/net_cloudnat/examples/hybrid.yaml new file mode 100644 index 0000000000..4dd1939598 --- /dev/null +++ b/tests/modules/net_cloudnat/examples/hybrid.yaml @@ -0,0 +1,94 @@ +# Copyright 2024 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.vpc1-nat.google_compute_router.router[0]: + bgp: [] + description: null + encrypted_interconnect_router: null + name: vpc1-nat-nat + project: project-id + region: europe-west8 + module.vpc1-nat.google_compute_router_nat.nat: + drain_nat_ips: null + enable_dynamic_port_allocation: true + enable_endpoint_independent_mapping: false + icmp_idle_timeout_sec: 30 + log_config: + - enable: false + filter: ALL + max_ports_per_vm: null + name: vpc1-nat + nat_ip_allocate_option: null + nat_ips: null + project: project-id + region: europe-west8 + router: vpc1-nat-nat + rules: + - action: + - source_nat_active_ips: [] + source_nat_drain_ips: [] + source_nat_drain_ranges: [] + description: private nat + match: nexthop.is_hybrid + rule_number: 0 + source_subnetwork_ip_ranges_to_nat: LIST_OF_SUBNETWORKS + subnetwork: + - secondary_ip_range_names: [] + source_ip_ranges_to_nat: + - ALL_IP_RANGES + tcp_established_idle_timeout_sec: 1200 + tcp_time_wait_timeout_sec: 120 + tcp_transitory_idle_timeout_sec: 30 + type: PRIVATE + udp_idle_timeout_sec: 30 + module.vpc1.google_compute_network.network[0]: + auto_create_subnetworks: false + delete_default_routes_on_create: false + description: Terraform-managed. + enable_ula_internal_ipv6: null + name: vpc1 + network_firewall_policy_enforcement_order: AFTER_CLASSIC_FIREWALL + project: project-id + routing_mode: GLOBAL + module.vpc1.google_compute_subnetwork.private_nat["europe-west8/vpc1-nat"]: + description: Terraform-managed private NAT subnet. + ip_cidr_range: 192.168.15.0/24 + ipv6_access_type: null + log_config: [] + name: vpc1-nat + network: vpc1 + project: project-id + purpose: PRIVATE_NAT + region: europe-west8 + role: null + module.vpc1.google_compute_subnetwork.subnetwork["europe-west8/vpc1-subnet"]: + description: Terraform-managed. + ip_cidr_range: 10.0.0.0/24 + ipv6_access_type: null + log_config: [] + name: vpc1-subnet + network: vpc1 + private_ip_google_access: true + project: project-id + region: europe-west8 + role: null + secondary_ip_range: [] + +counts: + google_compute_network: 1 + google_compute_route: 2 + google_compute_router: 1 + google_compute_router_nat: 1 + google_compute_subnetwork: 2 From d838c4ac478ea71ec30f69c06dcbe7173b07f08d Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 9 May 2024 18:29:25 +0200 Subject: [PATCH 18/31] Make Simple NVA route IAP traffic through NIC 0 (#2262) --- .../simple-nva/cloud-config.yaml | 5 +- .../s2_networking_e_nva_bgp/simple.yaml | 1889 +++++++++-------- 2 files changed, 949 insertions(+), 945 deletions(-) diff --git a/modules/cloud-config-container/simple-nva/cloud-config.yaml b/modules/cloud-config-container/simple-nva/cloud-config.yaml index 328ace7e2c..b5553793c0 100644 --- a/modules/cloud-config-container/simple-nva/cloud-config.yaml +++ b/modules/cloud-config-container/simple-nva/cloud-config.yaml @@ -1,6 +1,6 @@ #cloud-config -# Copyright 2023 Google LLC +# Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -51,6 +51,9 @@ write_files: %{ endfor ~} iptables -t nat -A POSTROUTING -o ${interface.name} -j MASQUERADE %{ endif ~} +%{ if interface.number == 0 ~} + ip route add 35.235.240.0/20 via `curl http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/gateway -H "Metadata-Flavor:Google"` dev ${interface.name} +%{ endif ~} %{ for route in interface.routes ~} ip route add ${route} via `curl http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/${interface.number}/gateway -H "Metadata-Flavor:Google"` dev ${interface.name} %{ endfor ~} diff --git a/tests/fast/stages/s2_networking_e_nva_bgp/simple.yaml b/tests/fast/stages/s2_networking_e_nva_bgp/simple.yaml index 38a23789d5..25ba7ccf9b 100644 --- a/tests/fast/stages/s2_networking_e_nva_bgp/simple.yaml +++ b/tests/fast/stages/s2_networking_e_nva_bgp/simple.yaml @@ -10,7 +10,6 @@ # 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: google_compute_address.nva_static_ip_dmz["primary-b"]: @@ -113,22 +112,21 @@ values: alert_strategy: [] combiner: OR conditions: - - condition_absent: [] - condition_matched_log: [] - condition_monitoring_query_language: - - duration: 120s - evaluation_missing_data: null - query: - fetch vpn_gateway| { metric vpn.googleapis.com/network/sent_bytes_count; - metric vpn.googleapis.com/network/received_bytes_count }| align rate (1m)| - group_by [metric.tunnel_name]| outer_join 0,0| value val(0) + val(1)| condition - val() > 187.5 "MBy/s" - trigger: - - count: 1 - percent: null - condition_prometheus_query_language: [] - condition_threshold: [] - display_name: VPN Tunnel Bandwidth usage + - condition_absent: [] + condition_matched_log: [] + condition_monitoring_query_language: + - duration: 120s + evaluation_missing_data: null + query: fetch vpn_gateway| { metric vpn.googleapis.com/network/sent_bytes_count; + metric vpn.googleapis.com/network/received_bytes_count }| align rate (1m)| + group_by [metric.tunnel_name]| outer_join 0,0| value val(0) + val(1)| condition + val() > 187.5 "MBy/s" + trigger: + - count: 1 + percent: null + condition_prometheus_query_language: [] + condition_threshold: [] + display_name: VPN Tunnel Bandwidth usage display_name: VPN Tunnel Bandwidth usage documentation: [] enabled: true @@ -141,21 +139,20 @@ values: alert_strategy: [] combiner: OR conditions: - - condition_absent: [] - condition_matched_log: [] - condition_monitoring_query_language: - - duration: 120s - evaluation_missing_data: null - query: - "fetch vpn_gateway| metric vpn.googleapis.com/tunnel_established| group_by - 5m, [value_tunnel_established_max: max(value.tunnel_established)]| every - 5m| condition val() < 1 '1'" - trigger: - - count: 1 - percent: null - condition_prometheus_query_language: [] - condition_threshold: [] - display_name: VPN Tunnel Established + - condition_absent: [] + condition_matched_log: [] + condition_monitoring_query_language: + - duration: 120s + evaluation_missing_data: null + query: 'fetch vpn_gateway| metric vpn.googleapis.com/tunnel_established| group_by + 5m, [value_tunnel_established_max: max(value.tunnel_established)]| every + 5m| condition val() < 1 ''1''' + trigger: + - count: 1 + percent: null + condition_prometheus_query_language: [] + condition_threshold: [] + display_name: VPN Tunnel Established display_name: VPN Tunnel Established documentation: [] enabled: true @@ -165,17 +162,15 @@ values: timeouts: null user_labels: null google_monitoring_dashboard.dashboard["firewall_insights.json"]: - dashboard_json: - '{"displayName":"Firewall Insights Monitoring","gridLayout":{"columns":"2","widgets":[{"title":"Subnet + dashboard_json: '{"displayName":"Firewall Insights Monitoring","gridLayout":{"columns":"2","widgets":[{"title":"Subnet Firewall Hit Counts","xyChart":{"chartOptions":{"mode":"COLOR"},"dataSets":[{"minAlignmentPeriod":"60s","plotType":"LINE","targetAxis":"Y1","timeSeriesQuery":{"timeSeriesFilter":{"aggregation":{"perSeriesAligner":"ALIGN_RATE"},"filter":"metric.type=\"firewallinsights.googleapis.com/subnet/firewall_hit_count\" resource.type=\"gce_subnetwork\"","secondaryAggregation":{}},"unitOverride":"1"}}],"timeshiftDuration":"0s","yAxis":{"label":"y1Axis","scale":"LINEAR"}}},{"title":"VM Firewall Hit Counts","xyChart":{"chartOptions":{"mode":"COLOR"},"dataSets":[{"minAlignmentPeriod":"60s","plotType":"LINE","targetAxis":"Y1","timeSeriesQuery":{"timeSeriesFilter":{"aggregation":{"perSeriesAligner":"ALIGN_RATE"},"filter":"metric.type=\"firewallinsights.googleapis.com/vm/firewall_hit_count\" resource.type=\"gce_instance\"","secondaryAggregation":{}},"unitOverride":"1"}}],"timeshiftDuration":"0s","yAxis":{"label":"y1Axis","scale":"LINEAR"}}}]}}' project: fast2-prod-net-landing-0 timeouts: null - ? google_monitoring_dashboard.dashboard["vpc_and_vpc_peering_group_quotas.json"] - : dashboard_json: - '{"dashboardFilters":[],"displayName":"VPC \u0026 VPC Peering + google_monitoring_dashboard.dashboard["vpc_and_vpc_peering_group_quotas.json"]: + dashboard_json: '{"dashboardFilters":[],"displayName":"VPC \u0026 VPC Peering Group Quotas","labels":{},"mosaicLayout":{"columns":12,"tiles":[{"height":4,"widget":{"title":"Internal network (L4) Load Balancers per VPC Peering Group","xyChart":{"chartOptions":{"mode":"COLOR"},"dataSets":[{"breakdowns":[],"dimensions":[],"measures":[],"plotType":"LINE","targetAxis":"Y1","timeSeriesQuery":{"timeSeriesQueryLanguage":"fetch compute.googleapis.com/VpcNetwork\n|{ metric\n compute.googleapis.com/quota/internal_lb_forwarding_rules_per_peering_group/usage\n | @@ -228,8 +223,7 @@ values: project: fast2-prod-net-landing-0 timeouts: null google_monitoring_dashboard.dashboard["vpn.json"]: - dashboard_json: - '{"displayName":"VPN Monitoring","mosaicLayout":{"columns":12,"tiles":[{"height":4,"widget":{"title":"Number + dashboard_json: '{"displayName":"VPN Monitoring","mosaicLayout":{"columns":12,"tiles":[{"height":4,"widget":{"title":"Number of connections","xyChart":{"chartOptions":{"mode":"COLOR"},"dataSets":[{"minAlignmentPeriod":"60s","plotType":"LINE","targetAxis":"Y1","timeSeriesQuery":{"timeSeriesFilter":{"aggregation":{"perSeriesAligner":"ALIGN_MEAN"},"filter":"metric.type=\"vpn.googleapis.com/gateway/connections\" resource.type=\"vpn_gateway\"","secondaryAggregation":{}},"unitOverride":"1"}}],"timeshiftDuration":"0s","yAxis":{"label":"y1Axis","scale":"LINEAR"}}},"width":4},{"height":4,"widget":{"title":"Tunnel established","xyChart":{"chartOptions":{"mode":"COLOR"},"dataSets":[{"minAlignmentPeriod":"60s","plotType":"LINE","targetAxis":"Y1","timeSeriesQuery":{"timeSeriesFilter":{"aggregation":{"perSeriesAligner":"ALIGN_MEAN"},"filter":"metric.type=\"vpn.googleapis.com/tunnel_established\" @@ -279,9 +273,9 @@ values: source: null temporary_hold: null timeouts: null - ? module.dev-dns-peer-landing-rev-10.google_dns_managed_zone.dns_managed_zone[0] - : cloud_logging_config: - - enable_logging: false + module.dev-dns-peer-landing-rev-10.google_dns_managed_zone.dns_managed_zone[0]: + cloud_logging_config: + - enable_logging: false description: Terraform managed. dns_name: 10.in-addr.arpa. dnssec_config: [] @@ -296,7 +290,7 @@ values: visibility: private module.dev-dns-peer-landing-root.google_dns_managed_zone.dns_managed_zone[0]: cloud_logging_config: - - enable_logging: false + - enable_logging: false description: Terraform managed. dns_name: . dnssec_config: [] @@ -311,7 +305,7 @@ values: visibility: private module.dev-dns-private-zone.google_dns_managed_zone.dns_managed_zone[0]: cloud_logging_config: - - enable_logging: false + - enable_logging: false description: Terraform managed. dns_name: dev.gcp.example.com. dnssec_config: [] @@ -324,23 +318,23 @@ values: service_directory_config: [] timeouts: null visibility: private - ? module.dev-dns-private-zone.google_dns_record_set.dns_record_set["A localhost"] - : managed_zone: dev-gcp-example-com + module.dev-dns-private-zone.google_dns_record_set.dns_record_set["A localhost"]: + managed_zone: dev-gcp-example-com name: localhost.dev.gcp.example.com. project: fast2-dev-net-spoke-0 routing_policy: [] rrdatas: - - 127.0.0.1 + - 127.0.0.1 ttl: 300 type: A - ? module.dev-spoke-firewall.google_compute_firewall.custom-rules["ingress-allow-composer-nodes"] - : allow: - - ports: - - "80" - - "443" - - "3306" - - "3307" - protocol: tcp + module.dev-spoke-firewall.google_compute_firewall.custom-rules["ingress-allow-composer-nodes"]: + allow: + - ports: + - '80' + - '443' + - '3306' + - '3307' + protocol: tcp deny: [] description: Allow traffic to Composer nodes. direction: INGRESS @@ -353,17 +347,17 @@ values: source_ranges: null source_service_accounts: null source_tags: - - composer-worker + - composer-worker target_service_accounts: null target_tags: - - composer-worker - timeouts: null - ? module.dev-spoke-firewall.google_compute_firewall.custom-rules["ingress-allow-dataflow-load"] - : allow: - - ports: - - "12345" - - "12346" - protocol: tcp + - composer-worker + timeouts: null + module.dev-spoke-firewall.google_compute_firewall.custom-rules["ingress-allow-dataflow-load"]: + allow: + - ports: + - '12345' + - '12346' + protocol: tcp deny: [] description: Allow traffic to Dataflow nodes. direction: INGRESS @@ -376,37 +370,37 @@ values: source_ranges: null source_service_accounts: null source_tags: - - dataflow + - dataflow target_service_accounts: null target_tags: - - dataflow + - dataflow timeouts: null - ? module.dev-spoke-firewall.google_compute_firewall.custom-rules["ingress-default-deny"] - : allow: [] + module.dev-spoke-firewall.google_compute_firewall.custom-rules["ingress-default-deny"]: + allow: [] deny: - - ports: [] - protocol: all + - ports: [] + protocol: all description: Deny and log any unmatched ingress traffic. direction: INGRESS disabled: false log_config: - - metadata: EXCLUDE_ALL_METADATA + - metadata: EXCLUDE_ALL_METADATA name: ingress-default-deny network: dev-spoke-0 priority: 65535 project: fast2-dev-net-spoke-0 source_ranges: - - 0.0.0.0/0 + - 0.0.0.0/0 source_service_accounts: null source_tags: null target_service_accounts: null target_tags: null timeouts: null - ? module.dev-spoke-project.google_compute_shared_vpc_host_project.shared_vpc_host[0] - : project: fast2-dev-net-spoke-0 + module.dev-spoke-project.google_compute_shared_vpc_host_project.shared_vpc_host[0]: + project: fast2-dev-net-spoke-0 timeouts: null - ? module.dev-spoke-project.google_monitoring_monitored_project.primary["fast2-prod-net-landing-0"] - : metrics_scope: fast2-prod-net-landing-0 + module.dev-spoke-project.google_monitoring_monitored_project.primary["fast2-prod-net-landing-0"]: + metrics_scope: fast2-prod-net-landing-0 name: fast2-dev-net-spoke-0 timeouts: null module.dev-spoke-project.google_project.project[0]: @@ -419,69 +413,69 @@ values: project_id: fast2-dev-net-spoke-0 skip_delete: false timeouts: null - ? module.dev-spoke-project.google_project_iam_binding.authoritative["roles/dns.admin"] - : condition: [] + module.dev-spoke-project.google_project_iam_binding.authoritative["roles/dns.admin"]: + condition: [] members: - - serviceAccount:string + - serviceAccount:string project: fast2-dev-net-spoke-0 role: roles/dns.admin - ? module.dev-spoke-project.google_project_iam_binding.bindings["sa_delegated_grants"] - : condition: - - description: Development host project delegated grants. - expression: api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly(['roles/composer.sharedVpcAgent','roles/compute.networkUser','roles/compute.networkViewer','roles/container.hostServiceAgentUser','roles/multiclusterservicediscovery.serviceAgent','roles/vpcaccess.user']) - title: dev_stage3_sa_delegated_grants + module.dev-spoke-project.google_project_iam_binding.bindings["sa_delegated_grants"]: + condition: + - description: Development host project delegated grants. + expression: api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly(['roles/composer.sharedVpcAgent','roles/compute.networkUser','roles/compute.networkViewer','roles/container.hostServiceAgentUser','roles/multiclusterservicediscovery.serviceAgent','roles/vpcaccess.user']) + title: dev_stage3_sa_delegated_grants members: - - serviceAccount:string + - serviceAccount:string project: fast2-dev-net-spoke-0 role: roles/resourcemanager.projectIamAdmin module.dev-spoke-project.google_project_iam_member.servicenetworking[0]: condition: [] project: fast2-dev-net-spoke-0 role: roles/servicenetworking.serviceAgent - ? module.dev-spoke-project.google_project_service.project_services["compute.googleapis.com"] - : disable_dependent_services: false + module.dev-spoke-project.google_project_service.project_services["compute.googleapis.com"]: + disable_dependent_services: false disable_on_destroy: false project: fast2-dev-net-spoke-0 service: compute.googleapis.com timeouts: null - ? module.dev-spoke-project.google_project_service.project_services["dns.googleapis.com"] - : disable_dependent_services: false + module.dev-spoke-project.google_project_service.project_services["dns.googleapis.com"]: + disable_dependent_services: false disable_on_destroy: false project: fast2-dev-net-spoke-0 service: dns.googleapis.com timeouts: null - ? module.dev-spoke-project.google_project_service.project_services["iap.googleapis.com"] - : disable_dependent_services: false + module.dev-spoke-project.google_project_service.project_services["iap.googleapis.com"]: + disable_dependent_services: false disable_on_destroy: false project: fast2-dev-net-spoke-0 service: iap.googleapis.com timeouts: null - ? module.dev-spoke-project.google_project_service.project_services["networkmanagement.googleapis.com"] - : disable_dependent_services: false + module.dev-spoke-project.google_project_service.project_services["networkmanagement.googleapis.com"]: + disable_dependent_services: false disable_on_destroy: false project: fast2-dev-net-spoke-0 service: networkmanagement.googleapis.com timeouts: null - ? module.dev-spoke-project.google_project_service.project_services["servicenetworking.googleapis.com"] - : disable_dependent_services: false + module.dev-spoke-project.google_project_service.project_services["servicenetworking.googleapis.com"]: + disable_dependent_services: false disable_on_destroy: false project: fast2-dev-net-spoke-0 service: servicenetworking.googleapis.com timeouts: null - ? module.dev-spoke-project.google_project_service.project_services["stackdriver.googleapis.com"] - : disable_dependent_services: false + module.dev-spoke-project.google_project_service.project_services["stackdriver.googleapis.com"]: + disable_dependent_services: false disable_on_destroy: false project: fast2-dev-net-spoke-0 service: stackdriver.googleapis.com timeouts: null - ? module.dev-spoke-project.google_project_service.project_services["vpcaccess.googleapis.com"] - : disable_dependent_services: false + module.dev-spoke-project.google_project_service.project_services["vpcaccess.googleapis.com"]: + disable_dependent_services: false disable_on_destroy: false project: fast2-dev-net-spoke-0 service: vpcaccess.googleapis.com timeouts: null - ? module.dev-spoke-project.google_project_service_identity.jit_si["iap.googleapis.com"] - : project: fast2-dev-net-spoke-0 + module.dev-spoke-project.google_project_service_identity.jit_si["iap.googleapis.com"]: + project: fast2-dev-net-spoke-0 service: iap.googleapis.com timeouts: null module.dev-spoke-project.google_project_service_identity.servicenetworking[0]: @@ -525,8 +519,8 @@ values: project: fast2-dev-net-spoke-0 tags: null timeouts: null - ? module.dev-spoke-vpc.google_compute_subnetwork.subnetwork["europe-west1/dev-dataplatform"] - : description: Default subnet for dev Data Platform + module.dev-spoke-vpc.google_compute_subnetwork.subnetwork["europe-west1/dev-dataplatform"]: + description: Default subnet for dev Data Platform ip_cidr_range: 10.68.2.0/24 ipv6_access_type: null log_config: [] @@ -537,13 +531,13 @@ values: region: europe-west1 role: null secondary_ip_range: - - ip_cidr_range: 100.69.0.0/16 - range_name: pods - - ip_cidr_range: 100.71.2.0/24 - range_name: services + - ip_cidr_range: 100.69.0.0/16 + range_name: pods + - ip_cidr_range: 100.71.2.0/24 + range_name: services timeouts: null - ? module.dev-spoke-vpc.google_compute_subnetwork.subnetwork["europe-west1/dev-default"] - : description: Default europe-west1 subnet for dev + module.dev-spoke-vpc.google_compute_subnetwork.subnetwork["europe-west1/dev-default"]: + description: Default europe-west1 subnet for dev ip_cidr_range: 10.68.0.0/24 ipv6_access_type: null log_config: [] @@ -555,8 +549,8 @@ values: role: null secondary_ip_range: [] timeouts: null - ? module.dev-spoke-vpc.google_compute_subnetwork.subnetwork["europe-west1/dev-gke-nodes"] - : description: Default subnet for prod gke nodes + module.dev-spoke-vpc.google_compute_subnetwork.subnetwork["europe-west1/dev-gke-nodes"]: + description: Default subnet for prod gke nodes ip_cidr_range: 10.68.1.0/24 ipv6_access_type: null log_config: [] @@ -567,13 +561,13 @@ values: region: europe-west1 role: null secondary_ip_range: - - ip_cidr_range: 100.68.0.0/16 - range_name: pods - - ip_cidr_range: 100.71.1.0/24 - range_name: services + - ip_cidr_range: 100.68.0.0/16 + range_name: pods + - ip_cidr_range: 100.71.1.0/24 + range_name: services timeouts: null - ? module.dev-spoke-vpc.google_compute_subnetwork.subnetwork["europe-west4/dev-default"] - : description: Default europe-west4 subnet for dev + module.dev-spoke-vpc.google_compute_subnetwork.subnetwork["europe-west4/dev-default"]: + description: Default europe-west4 subnet for dev ip_cidr_range: 10.84.0.0/24 ipv6_access_type: null log_config: [] @@ -592,14 +586,14 @@ values: enable_logging: true name: dev-spoke-0 networks: - - {} + - {} project: fast2-dev-net-spoke-0 timeouts: null - ? module.dmz-firewall.google_compute_firewall.custom-rules["allow-hc-nva-ssh-dmz"] - : allow: - - ports: - - "22" - protocol: tcp + module.dmz-firewall.google_compute_firewall.custom-rules["allow-hc-nva-ssh-dmz"]: + allow: + - ports: + - '22' + protocol: tcp deny: [] description: Allow traffic from Google healthchecks to NVA appliances direction: INGRESS @@ -610,20 +604,20 @@ values: priority: 1000 project: fast2-prod-net-landing-0 source_ranges: - - 130.211.0.0/22 - - 209.85.152.0/22 - - 209.85.204.0/22 - - 35.191.0.0/16 + - 130.211.0.0/22 + - 209.85.152.0/22 + - 209.85.204.0/22 + - 35.191.0.0/16 source_service_accounts: null source_tags: null target_service_accounts: null target_tags: null timeouts: null - ? module.dmz-firewall.google_compute_firewall.custom-rules["allow-ncc-nva-bgp-dmz"] - : allow: - - ports: - - "179" - protocol: tcp + module.dmz-firewall.google_compute_firewall.custom-rules["allow-ncc-nva-bgp-dmz"]: + allow: + - ports: + - '179' + protocol: tcp deny: [] description: Allow BGP traffic from NCC Cloud Routers to NVAs direction: INGRESS @@ -634,21 +628,21 @@ values: priority: 1000 project: fast2-prod-net-landing-0 source_ranges: - - 10.128.0.201/32 - - 10.128.0.202/32 - - 10.128.32.201/32 - - 10.128.32.202/32 + - 10.128.0.201/32 + - 10.128.0.202/32 + - 10.128.32.201/32 + - 10.128.32.202/32 source_service_accounts: null source_tags: null target_service_accounts: null target_tags: - - nva + - nva timeouts: null - ? module.dmz-firewall.google_compute_firewall.custom-rules["allow-nva-nva-bgp-dmz"] - : allow: - - ports: - - "179" - protocol: tcp + module.dmz-firewall.google_compute_firewall.custom-rules["allow-nva-nva-bgp-dmz"]: + allow: + - ports: + - '179' + protocol: tcp deny: [] description: Allow BGP traffic from cross-regional NVAs direction: INGRESS @@ -661,27 +655,27 @@ values: source_ranges: null source_service_accounts: null source_tags: - - nva + - nva target_service_accounts: null target_tags: - - nva + - nva timeouts: null - ? module.dmz-firewall.google_compute_firewall.custom-rules["dmz-ingress-default-deny"] - : allow: [] + module.dmz-firewall.google_compute_firewall.custom-rules["dmz-ingress-default-deny"]: + allow: [] deny: - - ports: [] - protocol: all + - ports: [] + protocol: all description: Deny and log any unmatched ingress traffic. direction: INGRESS disabled: false log_config: - - metadata: EXCLUDE_ALL_METADATA + - metadata: EXCLUDE_ALL_METADATA name: dmz-ingress-default-deny network: prod-dmz-0 priority: 65535 project: fast2-prod-net-landing-0 source_ranges: - - 0.0.0.0/0 + - 0.0.0.0/0 source_service_accounts: null source_tags: null target_service_accounts: null @@ -698,8 +692,8 @@ values: project: fast2-prod-net-landing-0 routing_mode: GLOBAL timeouts: null - ? module.dmz-vpc.google_compute_subnetwork.subnetwork["europe-west1/dmz-default"] - : description: Default europe-west1 subnet for DMZ + module.dmz-vpc.google_compute_subnetwork.subnetwork["europe-west1/dmz-default"]: + description: Default europe-west1 subnet for DMZ ip_cidr_range: 10.64.128.0/24 ipv6_access_type: null log_config: [] @@ -711,8 +705,8 @@ values: role: null secondary_ip_range: [] timeouts: null - ? module.dmz-vpc.google_compute_subnetwork.subnetwork["europe-west4/dmz-default"] - : description: Default europe-west4 subnet for DMZ + module.dmz-vpc.google_compute_subnetwork.subnetwork["europe-west4/dmz-default"]: + description: Default europe-west4 subnet for DMZ ip_cidr_range: 10.80.128.0/24 ipv6_access_type: null log_config: [] @@ -731,117 +725,117 @@ values: enable_logging: true name: prod-dmz-0 networks: - - {} + - {} project: fast2-prod-net-landing-0 timeouts: null module.firewall-policy-default.google_compute_firewall_policy.hierarchical[0]: description: null short_name: net-default timeouts: null - ? module.firewall-policy-default.google_compute_firewall_policy_rule.hierarchical["ingress/allow-healthchecks"] - : action: allow + module.firewall-policy-default.google_compute_firewall_policy_rule.hierarchical["ingress/allow-healthchecks"]: + action: allow description: Enable SSH, HTTP and HTTPS healthchecks direction: INGRESS disabled: false enable_logging: null match: - - dest_address_groups: null - dest_fqdns: null - dest_ip_ranges: null - dest_region_codes: null - dest_threat_intelligences: null - layer4_configs: - - ip_protocol: tcp - ports: - - "22" - - "80" - - "443" - src_address_groups: null - src_fqdns: null - src_ip_ranges: - - 35.191.0.0/16 - - 130.211.0.0/22 - - 209.85.152.0/22 - - 209.85.204.0/22 - src_region_codes: null - src_threat_intelligences: null + - dest_address_groups: null + dest_fqdns: null + dest_ip_ranges: null + dest_region_codes: null + dest_threat_intelligences: null + layer4_configs: + - ip_protocol: tcp + ports: + - '22' + - '80' + - '443' + src_address_groups: null + src_fqdns: null + src_ip_ranges: + - 35.191.0.0/16 + - 130.211.0.0/22 + - 209.85.152.0/22 + - 209.85.204.0/22 + src_region_codes: null + src_threat_intelligences: null priority: 1001 target_resources: null target_service_accounts: null timeouts: null - ? module.firewall-policy-default.google_compute_firewall_policy_rule.hierarchical["ingress/allow-icmp"] - : action: allow + module.firewall-policy-default.google_compute_firewall_policy_rule.hierarchical["ingress/allow-icmp"]: + action: allow description: Enable ICMP direction: INGRESS disabled: false enable_logging: null match: - - dest_address_groups: null - dest_fqdns: null - dest_ip_ranges: null - dest_region_codes: null - dest_threat_intelligences: null - layer4_configs: - - ip_protocol: icmp - ports: [] - src_address_groups: null - src_fqdns: null - src_ip_ranges: - - 0.0.0.0/0 - src_region_codes: null - src_threat_intelligences: null + - dest_address_groups: null + dest_fqdns: null + dest_ip_ranges: null + dest_region_codes: null + dest_threat_intelligences: null + layer4_configs: + - ip_protocol: icmp + ports: [] + src_address_groups: null + src_fqdns: null + src_ip_ranges: + - 0.0.0.0/0 + src_region_codes: null + src_threat_intelligences: null priority: 1003 target_resources: null target_service_accounts: null timeouts: null - ? module.firewall-policy-default.google_compute_firewall_policy_rule.hierarchical["ingress/allow-nat-ranges"] - : action: allow + module.firewall-policy-default.google_compute_firewall_policy_rule.hierarchical["ingress/allow-nat-ranges"]: + action: allow description: Enable NAT ranges for VPC serverless connector direction: INGRESS disabled: false enable_logging: null match: - - dest_address_groups: null - dest_fqdns: null - dest_ip_ranges: null - dest_region_codes: null - dest_threat_intelligences: null - layer4_configs: - - ip_protocol: all - ports: null - src_address_groups: null - src_fqdns: null - src_ip_ranges: - - 107.178.230.64/26 - - 35.199.224.0/19 - src_region_codes: null - src_threat_intelligences: null + - dest_address_groups: null + dest_fqdns: null + dest_ip_ranges: null + dest_region_codes: null + dest_threat_intelligences: null + layer4_configs: + - ip_protocol: all + ports: null + src_address_groups: null + src_fqdns: null + src_ip_ranges: + - 107.178.230.64/26 + - 35.199.224.0/19 + src_region_codes: null + src_threat_intelligences: null priority: 1004 target_resources: null target_service_accounts: null timeouts: null - ? module.firewall-policy-default.google_compute_firewall_policy_rule.hierarchical["ingress/allow-ssh-from-iap"] - : action: allow + module.firewall-policy-default.google_compute_firewall_policy_rule.hierarchical["ingress/allow-ssh-from-iap"]: + action: allow description: Enable SSH from IAP direction: INGRESS disabled: false enable_logging: true match: - - dest_address_groups: null - dest_fqdns: null - dest_ip_ranges: null - dest_region_codes: null - dest_threat_intelligences: null - layer4_configs: - - ip_protocol: tcp - ports: - - "22" - src_address_groups: null - src_fqdns: null - src_ip_ranges: - - 35.235.240.0/20 - src_region_codes: null - src_threat_intelligences: null + - dest_address_groups: null + dest_fqdns: null + dest_ip_ranges: null + dest_region_codes: null + dest_threat_intelligences: null + layer4_configs: + - ip_protocol: tcp + ports: + - '22' + src_address_groups: null + src_fqdns: null + src_ip_ranges: + - 35.235.240.0/20 + src_region_codes: null + src_threat_intelligences: null priority: 1002 target_resources: null target_service_accounts: null @@ -849,27 +843,27 @@ values: module.folder.google_compute_firewall_policy_association.default[0]: name: default timeouts: null - ? module.folder.google_essential_contacts_contact.contact["gcp-network-admins@fast.example.com"] - : email: gcp-network-admins@fast.example.com + module.folder.google_essential_contacts_contact.contact["gcp-network-admins@fast.example.com"]: + email: gcp-network-admins@fast.example.com language_tag: en notification_category_subscriptions: - - ALL + - ALL timeouts: null module.folder.google_folder.folder[0]: display_name: Networking parent: organizations/123456789012 timeouts: null - ? module.landing-dns-fwd-onprem-example[0].google_dns_managed_zone.dns_managed_zone[0] - : cloud_logging_config: - - enable_logging: false + module.landing-dns-fwd-onprem-example[0].google_dns_managed_zone.dns_managed_zone[0]: + cloud_logging_config: + - enable_logging: false description: Terraform managed. dns_name: onprem.example.com. dnssec_config: [] force_destroy: false forwarding_config: - - target_name_servers: - - forwarding_path: "" - ipv4_address: 10.10.10.10 + - target_name_servers: + - forwarding_path: '' + ipv4_address: 10.10.10.10 labels: null name: example-com peering_config: [] @@ -878,17 +872,17 @@ values: service_directory_config: [] timeouts: null visibility: private - ? module.landing-dns-fwd-onprem-rev-10[0].google_dns_managed_zone.dns_managed_zone[0] - : cloud_logging_config: - - enable_logging: false + module.landing-dns-fwd-onprem-rev-10[0].google_dns_managed_zone.dns_managed_zone[0]: + cloud_logging_config: + - enable_logging: false description: Terraform managed. dns_name: 10.in-addr.arpa. dnssec_config: [] force_destroy: false forwarding_config: - - target_name_servers: - - forwarding_path: "" - ipv4_address: 10.10.10.10 + - target_name_servers: + - forwarding_path: '' + ipv4_address: 10.10.10.10 labels: null name: root-reverse-10 peering_config: [] @@ -901,496 +895,496 @@ values: description: Terraform managed. gke_clusters: [] networks: - - {} - - {} + - {} + - {} project: fast2-prod-net-landing-0 response_policy_name: googleapis timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["accounts"] - : behavior: null + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["accounts"]: + behavior: null dns_name: accounts.google.com. local_data: - - local_datas: - - name: accounts.google.com. - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: accounts.google.com. + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: accounts timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["backupdr-cloud"] - : behavior: null + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["backupdr-cloud"]: + behavior: null dns_name: backupdr.cloud.google.com. local_data: - - local_datas: - - name: backupdr.cloud.google.com. - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: backupdr.cloud.google.com. + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: backupdr-cloud timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["backupdr-cloud-all"] - : behavior: null - dns_name: "*.backupdr.cloud.google.com." + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["backupdr-cloud-all"]: + behavior: null + dns_name: '*.backupdr.cloud.google.com.' local_data: - - local_datas: - - name: "*.backupdr.cloud.google.com." - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: '*.backupdr.cloud.google.com.' + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: backupdr-cloud-all timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["backupdr-gu"] - : behavior: null + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["backupdr-gu"]: + behavior: null dns_name: backupdr.googleusercontent.google.com. local_data: - - local_datas: - - name: backupdr.googleusercontent.google.com. - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: backupdr.googleusercontent.google.com. + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: backupdr-gu timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["backupdr-gu-all"] - : behavior: null - dns_name: "*.backupdr.googleusercontent.google.com." + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["backupdr-gu-all"]: + behavior: null + dns_name: '*.backupdr.googleusercontent.google.com.' local_data: - - local_datas: - - name: "*.backupdr.googleusercontent.google.com." - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: '*.backupdr.googleusercontent.google.com.' + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: backupdr-gu-all timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["cloudfunctions"] - : behavior: null - dns_name: "*.cloudfunctions.net." + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["cloudfunctions"]: + behavior: null + dns_name: '*.cloudfunctions.net.' local_data: - - local_datas: - - name: "*.cloudfunctions.net." - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: '*.cloudfunctions.net.' + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: cloudfunctions timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["cloudproxy"] - : behavior: null - dns_name: "*.cloudproxy.app." + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["cloudproxy"]: + behavior: null + dns_name: '*.cloudproxy.app.' local_data: - - local_datas: - - name: "*.cloudproxy.app." - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: '*.cloudproxy.app.' + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: cloudproxy timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["composer-cloud-all"] - : behavior: null - dns_name: "*.composer.cloud.google.com." + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["composer-cloud-all"]: + behavior: null + dns_name: '*.composer.cloud.google.com.' local_data: - - local_datas: - - name: "*.composer.cloud.google.com." - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: '*.composer.cloud.google.com.' + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: composer-cloud-all timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["composer-gu-all"] - : behavior: null - dns_name: "*.composer.googleusercontent.com." + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["composer-gu-all"]: + behavior: null + dns_name: '*.composer.googleusercontent.com.' local_data: - - local_datas: - - name: "*.composer.googleusercontent.com." - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: '*.composer.googleusercontent.com.' + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: composer-gu-all timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["datafusion-all"] - : behavior: null - dns_name: "*.datafusion.cloud.google.com." + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["datafusion-all"]: + behavior: null + dns_name: '*.datafusion.cloud.google.com.' local_data: - - local_datas: - - name: "*.datafusion.cloud.google.com." - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: '*.datafusion.cloud.google.com.' + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: datafusion-all timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["datafusion-gu-all"] - : behavior: null - dns_name: "*.datafusion.googleusercontent.com." + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["datafusion-gu-all"]: + behavior: null + dns_name: '*.datafusion.googleusercontent.com.' local_data: - - local_datas: - - name: "*.datafusion.googleusercontent.com." - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: '*.datafusion.googleusercontent.com.' + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: datafusion-gu-all timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["dataproc"] - : behavior: null + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["dataproc"]: + behavior: null dns_name: dataproc.cloud.google.com. local_data: - - local_datas: - - name: dataproc.cloud.google.com. - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: dataproc.cloud.google.com. + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: dataproc timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["dataproc-all"] - : behavior: null - dns_name: "*.dataproc.cloud.google.com." + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["dataproc-all"]: + behavior: null + dns_name: '*.dataproc.cloud.google.com.' local_data: - - local_datas: - - name: "*.dataproc.cloud.google.com." - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: '*.dataproc.cloud.google.com.' + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: dataproc-all timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["dataproc-gu"] - : behavior: null + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["dataproc-gu"]: + behavior: null dns_name: dataproc.googleusercontent.com. local_data: - - local_datas: - - name: dataproc.googleusercontent.com. - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: dataproc.googleusercontent.com. + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: dataproc-gu timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["dataproc-gu-all"] - : behavior: null - dns_name: "*.dataproc.googleusercontent.com." + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["dataproc-gu-all"]: + behavior: null + dns_name: '*.dataproc.googleusercontent.com.' local_data: - - local_datas: - - name: "*.dataproc.googleusercontent.com." - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: '*.dataproc.googleusercontent.com.' + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: dataproc-gu-all timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["dl"] - : behavior: null + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["dl"]: + behavior: null dns_name: dl.google.com. local_data: - - local_datas: - - name: dl.google.com. - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: dl.google.com. + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: dl timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["gcr"] - : behavior: null + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["gcr"]: + behavior: null dns_name: gcr.io. local_data: - - local_datas: - - name: gcr.io. - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: gcr.io. + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: gcr timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["gcr-all"] - : behavior: null - dns_name: "*.gcr.io." + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["gcr-all"]: + behavior: null + dns_name: '*.gcr.io.' local_data: - - local_datas: - - name: "*.gcr.io." - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: '*.gcr.io.' + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: gcr-all timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["googleapis-all"] - : behavior: null - dns_name: "*.googleapis.com." + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["googleapis-all"]: + behavior: null + dns_name: '*.googleapis.com.' local_data: - - local_datas: - - name: "*.googleapis.com." - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: '*.googleapis.com.' + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: googleapis-all timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["googleapis-private"] - : behavior: null + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["googleapis-private"]: + behavior: null dns_name: private.googleapis.com. local_data: - - local_datas: - - name: private.googleapis.com. - rrdatas: - - 199.36.153.8 - - 199.36.153.9 - - 199.36.153.10 - - 199.36.153.11 - ttl: null - type: A + - local_datas: + - name: private.googleapis.com. + rrdatas: + - 199.36.153.8 + - 199.36.153.9 + - 199.36.153.10 + - 199.36.153.11 + ttl: null + type: A project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: googleapis-private timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["googleapis-restricted"] - : behavior: null + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["googleapis-restricted"]: + behavior: null dns_name: restricted.googleapis.com. local_data: - - local_datas: - - name: restricted.googleapis.com. - rrdatas: - - 199.36.153.4 - - 199.36.153.5 - - 199.36.153.6 - - 199.36.153.7 - ttl: null - type: A + - local_datas: + - name: restricted.googleapis.com. + rrdatas: + - 199.36.153.4 + - 199.36.153.5 + - 199.36.153.6 + - 199.36.153.7 + ttl: null + type: A project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: googleapis-restricted timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["gstatic-all"] - : behavior: null - dns_name: "*.gstatic.com." + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["gstatic-all"]: + behavior: null + dns_name: '*.gstatic.com.' local_data: - - local_datas: - - name: "*.gstatic.com." - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: '*.gstatic.com.' + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: gstatic-all timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["kernels-gu"] - : behavior: null + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["kernels-gu"]: + behavior: null dns_name: kernels.googleusercontent.com. local_data: - - local_datas: - - name: kernels.googleusercontent.com. - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: kernels.googleusercontent.com. + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: kernels-gu timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["kernels-gu-all"] - : behavior: null - dns_name: "*.kernels.googleusercontent.com." + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["kernels-gu-all"]: + behavior: null + dns_name: '*.kernels.googleusercontent.com.' local_data: - - local_datas: - - name: "*.kernels.googleusercontent.com." - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: '*.kernels.googleusercontent.com.' + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: kernels-gu-all timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["notebooks-all"] - : behavior: null - dns_name: "*.notebooks.cloud.google.com." + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["notebooks-all"]: + behavior: null + dns_name: '*.notebooks.cloud.google.com.' local_data: - - local_datas: - - name: "*.notebooks.cloud.google.com." - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: '*.notebooks.cloud.google.com.' + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: notebooks-all timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["notebooks-gu-all"] - : behavior: null - dns_name: "*.notebooks.googleusercontent.com." + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["notebooks-gu-all"]: + behavior: null + dns_name: '*.notebooks.googleusercontent.com.' local_data: - - local_datas: - - name: "*.notebooks.googleusercontent.com." - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: '*.notebooks.googleusercontent.com.' + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: notebooks-gu-all timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["packages-cloud"] - : behavior: null + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["packages-cloud"]: + behavior: null dns_name: packages.cloud.google.com. local_data: - - local_datas: - - name: packages.cloud.google.com. - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: packages.cloud.google.com. + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: packages-cloud timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["packages-cloud-all"] - : behavior: null - dns_name: "*.packages.cloud.google.com." + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["packages-cloud-all"]: + behavior: null + dns_name: '*.packages.cloud.google.com.' local_data: - - local_datas: - - name: "*.packages.cloud.google.com." - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: '*.packages.cloud.google.com.' + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: packages-cloud-all timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["pkgdev"] - : behavior: null + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["pkgdev"]: + behavior: null dns_name: pkg.dev. local_data: - - local_datas: - - name: pkg.dev. - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: pkg.dev. + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: pkgdev timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["pkgdev-all"] - : behavior: null - dns_name: "*.pkg.dev." + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["pkgdev-all"]: + behavior: null + dns_name: '*.pkg.dev.' local_data: - - local_datas: - - name: "*.pkg.dev." - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: '*.pkg.dev.' + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: pkgdev-all timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["pkigoog"] - : behavior: null + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["pkigoog"]: + behavior: null dns_name: pki.goog. local_data: - - local_datas: - - name: pki.goog. - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: pki.goog. + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: pkigoog timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["pkigoog-all"] - : behavior: null - dns_name: "*.pki.goog." + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["pkigoog-all"]: + behavior: null + dns_name: '*.pki.goog.' local_data: - - local_datas: - - name: "*.pki.goog." - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: '*.pki.goog.' + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: pkigoog-all timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["run-all"] - : behavior: null - dns_name: "*.run.app." + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["run-all"]: + behavior: null + dns_name: '*.run.app.' local_data: - - local_datas: - - name: "*.run.app." - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: '*.run.app.' + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: run-all timeouts: null - ? module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["source"] - : behavior: null + module.landing-dns-policy-googleapis.google_dns_response_policy_rule.default["source"]: + behavior: null dns_name: source.developers.google.com. local_data: - - local_datas: - - name: source.developers.google.com. - rrdatas: - - private.googleapis.com. - ttl: null - type: CNAME + - local_datas: + - name: source.developers.google.com. + rrdatas: + - private.googleapis.com. + ttl: null + type: CNAME project: fast2-prod-net-landing-0 response_policy: googleapis rule_name: source timeouts: null module.landing-dns-priv-gcp.google_dns_managed_zone.dns_managed_zone[0]: cloud_logging_config: - - enable_logging: false + - enable_logging: false description: Terraform managed. dns_name: gcp.example.com. dnssec_config: [] @@ -1403,20 +1397,20 @@ values: service_directory_config: [] timeouts: null visibility: private - ? module.landing-dns-priv-gcp.google_dns_record_set.dns_record_set["A localhost"] - : managed_zone: gcp-example-com + module.landing-dns-priv-gcp.google_dns_record_set.dns_record_set["A localhost"]: + managed_zone: gcp-example-com name: localhost.gcp.example.com. project: fast2-prod-net-landing-0 routing_policy: [] rrdatas: - - 127.0.0.1 + - 127.0.0.1 ttl: 300 type: A - ? module.landing-firewall.google_compute_firewall.custom-rules["allow-hc-nva-ssh-landing"] - : allow: - - ports: - - "22" - protocol: tcp + module.landing-firewall.google_compute_firewall.custom-rules["allow-hc-nva-ssh-landing"]: + allow: + - ports: + - '22' + protocol: tcp deny: [] description: Allow traffic from Google healthchecks to NVA appliances direction: INGRESS @@ -1427,20 +1421,20 @@ values: priority: 1000 project: fast2-prod-net-landing-0 source_ranges: - - 130.211.0.0/22 - - 209.85.152.0/22 - - 209.85.204.0/22 - - 35.191.0.0/16 + - 130.211.0.0/22 + - 209.85.152.0/22 + - 209.85.204.0/22 + - 35.191.0.0/16 source_service_accounts: null source_tags: null target_service_accounts: null target_tags: null timeouts: null - ? module.landing-firewall.google_compute_firewall.custom-rules["allow-ncc-nva-bgp-landing"] - : allow: - - ports: - - "179" - protocol: tcp + module.landing-firewall.google_compute_firewall.custom-rules["allow-ncc-nva-bgp-landing"]: + allow: + - ports: + - '179' + protocol: tcp deny: [] description: Allow BGP traffic from NCC Cloud Routers to NVAs direction: INGRESS @@ -1451,21 +1445,21 @@ values: priority: 1000 project: fast2-prod-net-landing-0 source_ranges: - - 10.128.64.201/32 - - 10.128.64.202/32 - - 10.128.96.201/32 - - 10.128.96.202/32 + - 10.128.64.201/32 + - 10.128.64.202/32 + - 10.128.96.201/32 + - 10.128.96.202/32 source_service_accounts: null source_tags: null target_service_accounts: null target_tags: - - nva + - nva timeouts: null - ? module.landing-firewall.google_compute_firewall.custom-rules["allow-onprem-probes-landing-example"] - : allow: - - ports: - - "12345" - protocol: tcp + module.landing-firewall.google_compute_firewall.custom-rules["allow-onprem-probes-landing-example"]: + allow: + - ports: + - '12345' + protocol: tcp deny: [] description: Allow traffic from onprem probes direction: INGRESS @@ -1476,28 +1470,28 @@ values: priority: 1000 project: fast2-prod-net-landing-0 source_ranges: - - 10.255.255.254/32 + - 10.255.255.254/32 source_service_accounts: null source_tags: null target_service_accounts: null target_tags: null timeouts: null - ? module.landing-firewall.google_compute_firewall.custom-rules["landing-ingress-default-deny"] - : allow: [] + module.landing-firewall.google_compute_firewall.custom-rules["landing-ingress-default-deny"]: + allow: [] deny: - - ports: [] - protocol: all + - ports: [] + protocol: all description: Deny and log any unmatched ingress traffic. direction: INGRESS disabled: false log_config: - - metadata: EXCLUDE_ALL_METADATA + - metadata: EXCLUDE_ALL_METADATA name: landing-ingress-default-deny network: prod-landing-0 priority: 65535 project: fast2-prod-net-landing-0 source_ranges: - - 0.0.0.0/0 + - 0.0.0.0/0 source_service_accounts: null source_tags: null target_service_accounts: null @@ -1518,8 +1512,8 @@ values: enable_endpoint_independent_mapping: true icmp_idle_timeout_sec: 30 log_config: - - enable: false - filter: ALL + - enable: false + filter: ALL max_ports_per_vm: null name: ew1 nat_ip_allocate_option: AUTO_ONLY @@ -1534,6 +1528,7 @@ values: tcp_time_wait_timeout_sec: 120 tcp_transitory_idle_timeout_sec: 30 timeouts: null + type: PUBLIC udp_idle_timeout_sec: 30 module.landing-nat-secondary[0].google_compute_router.router[0]: bgp: [] @@ -1550,8 +1545,8 @@ values: enable_endpoint_independent_mapping: true icmp_idle_timeout_sec: 30 log_config: - - enable: false - filter: ALL + - enable: false + filter: ALL max_ports_per_vm: null name: ew4 nat_ip_allocate_option: AUTO_ONLY @@ -1566,9 +1561,10 @@ values: tcp_time_wait_timeout_sec: 120 tcp_transitory_idle_timeout_sec: 30 timeouts: null + type: PUBLIC udp_idle_timeout_sec: 30 - ? module.landing-project.google_compute_shared_vpc_host_project.shared_vpc_host[0] - : project: fast2-prod-net-landing-0 + module.landing-project.google_compute_shared_vpc_host_project.shared_vpc_host[0]: + project: fast2-prod-net-landing-0 timeouts: null module.landing-project.google_project.project[0]: auto_create_network: false @@ -1580,70 +1576,70 @@ values: project_id: fast2-prod-net-landing-0 skip_delete: false timeouts: null - ? module.landing-project.google_project_iam_binding.authoritative["organizations/123456789012/roles/foo"] - : condition: [] + module.landing-project.google_project_iam_binding.authoritative["organizations/123456789012/roles/foo"]: + condition: [] members: - - serviceAccount:string + - serviceAccount:string project: fast2-prod-net-landing-0 role: organizations/123456789012/roles/foo - ? module.landing-project.google_project_iam_binding.authoritative["roles/dns.admin"] - : condition: [] + module.landing-project.google_project_iam_binding.authoritative["roles/dns.admin"]: + condition: [] members: - - serviceAccount:string + - serviceAccount:string project: fast2-prod-net-landing-0 role: roles/dns.admin - ? module.landing-project.google_project_service.project_services["compute.googleapis.com"] - : disable_dependent_services: false + module.landing-project.google_project_service.project_services["compute.googleapis.com"]: + disable_dependent_services: false disable_on_destroy: false project: fast2-prod-net-landing-0 service: compute.googleapis.com timeouts: null - ? module.landing-project.google_project_service.project_services["dns.googleapis.com"] - : disable_dependent_services: false + module.landing-project.google_project_service.project_services["dns.googleapis.com"]: + disable_dependent_services: false disable_on_destroy: false project: fast2-prod-net-landing-0 service: dns.googleapis.com timeouts: null - ? module.landing-project.google_project_service.project_services["iap.googleapis.com"] - : disable_dependent_services: false + module.landing-project.google_project_service.project_services["iap.googleapis.com"]: + disable_dependent_services: false disable_on_destroy: false project: fast2-prod-net-landing-0 service: iap.googleapis.com timeouts: null - ? module.landing-project.google_project_service.project_services["networkconnectivity.googleapis.com"] - : disable_dependent_services: false + module.landing-project.google_project_service.project_services["networkconnectivity.googleapis.com"]: + disable_dependent_services: false disable_on_destroy: false project: fast2-prod-net-landing-0 service: networkconnectivity.googleapis.com timeouts: null - ? module.landing-project.google_project_service.project_services["networkmanagement.googleapis.com"] - : disable_dependent_services: false + module.landing-project.google_project_service.project_services["networkmanagement.googleapis.com"]: + disable_dependent_services: false disable_on_destroy: false project: fast2-prod-net-landing-0 service: networkmanagement.googleapis.com timeouts: null - ? module.landing-project.google_project_service.project_services["stackdriver.googleapis.com"] - : disable_dependent_services: false + module.landing-project.google_project_service.project_services["stackdriver.googleapis.com"]: + disable_dependent_services: false disable_on_destroy: false project: fast2-prod-net-landing-0 service: stackdriver.googleapis.com timeouts: null - ? module.landing-project.google_project_service_identity.jit_si["iap.googleapis.com"] - : project: fast2-prod-net-landing-0 + module.landing-project.google_project_service_identity.jit_si["iap.googleapis.com"]: + project: fast2-prod-net-landing-0 service: iap.googleapis.com timeouts: null - ? module.landing-to-onprem-primary-vpn[0].google_compute_external_vpn_gateway.external_gateway["default"] - : description: Terraform managed external VPN gateway + module.landing-to-onprem-primary-vpn[0].google_compute_external_vpn_gateway.external_gateway["default"]: + description: Terraform managed external VPN gateway interface: - - id: 0 - ip_address: 8.8.8.8 + - id: 0 + ip_address: 8.8.8.8 labels: null name: vpn-to-onprem-ew1-default project: fast2-prod-net-landing-0 redundancy_type: SINGLE_IP_INTERNALLY_REDUNDANT timeouts: null - ? module.landing-to-onprem-primary-vpn[0].google_compute_ha_vpn_gateway.ha_gateway[0] - : description: Terraform managed external VPN gateway + module.landing-to-onprem-primary-vpn[0].google_compute_ha_vpn_gateway.ha_gateway[0]: + description: Terraform managed external VPN gateway name: vpn-to-onprem-ew1 project: fast2-prod-net-landing-0 region: europe-west1 @@ -1651,25 +1647,25 @@ values: timeouts: null module.landing-to-onprem-primary-vpn[0].google_compute_router.router[0]: bgp: - - advertise_mode: CUSTOM - advertised_groups: [] - advertised_ip_ranges: - - description: gcp - range: 10.1.0.0/16 - - description: gcp-restricted - range: 199.36.153.4/30 - - description: gcp-dns - range: 35.199.192.0/19 - asn: 65501 - keepalive_interval: 20 + - advertise_mode: CUSTOM + advertised_groups: [] + advertised_ip_ranges: + - description: gcp + range: 10.1.0.0/16 + - description: gcp-restricted + range: 199.36.153.4/30 + - description: gcp-dns + range: 35.199.192.0/19 + asn: 65501 + keepalive_interval: 20 description: null encrypted_interconnect_router: null name: vpn-vpn-to-onprem-ew1 project: fast2-prod-net-landing-0 region: europe-west1 timeouts: null - ? module.landing-to-onprem-primary-vpn[0].google_compute_router_interface.router_interface["0"] - : interconnect_attachment: null + module.landing-to-onprem-primary-vpn[0].google_compute_router_interface.router_interface["0"]: + interconnect_attachment: null ip_range: 169.254.1.2/30 name: vpn-to-onprem-ew1-0 private_ip_address: null @@ -1679,8 +1675,8 @@ values: subnetwork: null timeouts: null vpn_tunnel: vpn-to-onprem-ew1-0 - ? module.landing-to-onprem-primary-vpn[0].google_compute_router_interface.router_interface["1"] - : interconnect_attachment: null + module.landing-to-onprem-primary-vpn[0].google_compute_router_interface.router_interface["1"]: + interconnect_attachment: null ip_range: 169.254.2.2/30 name: vpn-to-onprem-ew1-1 private_ip_address: null @@ -1690,8 +1686,8 @@ values: subnetwork: null timeouts: null vpn_tunnel: vpn-to-onprem-ew1-1 - ? module.landing-to-onprem-primary-vpn[0].google_compute_router_peer.bgp_peer["0"] - : advertise_mode: DEFAULT + module.landing-to-onprem-primary-vpn[0].google_compute_router_peer.bgp_peer["0"]: + advertise_mode: DEFAULT advertised_groups: [] advertised_ip_ranges: [] advertised_route_priority: 1000 @@ -1707,8 +1703,8 @@ values: router: vpn-vpn-to-onprem-ew1 router_appliance_instance: null timeouts: null - ? module.landing-to-onprem-primary-vpn[0].google_compute_router_peer.bgp_peer["1"] - : advertise_mode: DEFAULT + module.landing-to-onprem-primary-vpn[0].google_compute_router_peer.bgp_peer["1"]: + advertise_mode: DEFAULT advertised_groups: [] advertised_ip_ranges: [] advertised_route_priority: 1000 @@ -1724,8 +1720,8 @@ values: router: vpn-vpn-to-onprem-ew1 router_appliance_instance: null timeouts: null - ? module.landing-to-onprem-primary-vpn[0].google_compute_vpn_tunnel.tunnels["0"] - : description: null + module.landing-to-onprem-primary-vpn[0].google_compute_vpn_tunnel.tunnels["0"]: + description: null ike_version: 2 labels: null name: vpn-to-onprem-ew1-0 @@ -1738,8 +1734,8 @@ values: target_vpn_gateway: null timeouts: null vpn_gateway_interface: 0 - ? module.landing-to-onprem-primary-vpn[0].google_compute_vpn_tunnel.tunnels["1"] - : description: null + module.landing-to-onprem-primary-vpn[0].google_compute_vpn_tunnel.tunnels["1"]: + description: null ike_version: 2 labels: null name: vpn-to-onprem-ew1-1 @@ -1756,18 +1752,18 @@ values: byte_length: 8 keepers: null prefix: null - ? module.landing-to-onprem-secondary-vpn[0].google_compute_external_vpn_gateway.external_gateway["default"] - : description: Terraform managed external VPN gateway + module.landing-to-onprem-secondary-vpn[0].google_compute_external_vpn_gateway.external_gateway["default"]: + description: Terraform managed external VPN gateway interface: - - id: 0 - ip_address: 8.8.4.4 + - id: 0 + ip_address: 8.8.4.4 labels: null name: vpn-to-onprem-ew4-default project: fast2-prod-net-landing-0 redundancy_type: SINGLE_IP_INTERNALLY_REDUNDANT timeouts: null - ? module.landing-to-onprem-secondary-vpn[0].google_compute_ha_vpn_gateway.ha_gateway[0] - : description: Terraform managed external VPN gateway + module.landing-to-onprem-secondary-vpn[0].google_compute_ha_vpn_gateway.ha_gateway[0]: + description: Terraform managed external VPN gateway name: vpn-to-onprem-ew4 project: fast2-prod-net-landing-0 region: europe-west4 @@ -1775,25 +1771,25 @@ values: timeouts: null module.landing-to-onprem-secondary-vpn[0].google_compute_router.router[0]: bgp: - - advertise_mode: CUSTOM - advertised_groups: [] - advertised_ip_ranges: - - description: gcp - range: 10.1.0.0/16 - - description: gcp-restricted - range: 199.36.153.4/30 - - description: gcp-dns - range: 35.199.192.0/19 - asn: 65501 - keepalive_interval: 20 + - advertise_mode: CUSTOM + advertised_groups: [] + advertised_ip_ranges: + - description: gcp + range: 10.1.0.0/16 + - description: gcp-restricted + range: 199.36.153.4/30 + - description: gcp-dns + range: 35.199.192.0/19 + asn: 65501 + keepalive_interval: 20 description: null encrypted_interconnect_router: null name: vpn-vpn-to-onprem-ew4 project: fast2-prod-net-landing-0 region: europe-west4 timeouts: null - ? module.landing-to-onprem-secondary-vpn[0].google_compute_router_interface.router_interface["0"] - : interconnect_attachment: null + module.landing-to-onprem-secondary-vpn[0].google_compute_router_interface.router_interface["0"]: + interconnect_attachment: null ip_range: 169.254.3.2/30 name: vpn-to-onprem-ew4-0 private_ip_address: null @@ -1803,8 +1799,8 @@ values: subnetwork: null timeouts: null vpn_tunnel: vpn-to-onprem-ew4-0 - ? module.landing-to-onprem-secondary-vpn[0].google_compute_router_interface.router_interface["1"] - : interconnect_attachment: null + module.landing-to-onprem-secondary-vpn[0].google_compute_router_interface.router_interface["1"]: + interconnect_attachment: null ip_range: 169.254.4.2/30 name: vpn-to-onprem-ew4-1 private_ip_address: null @@ -1814,8 +1810,8 @@ values: subnetwork: null timeouts: null vpn_tunnel: vpn-to-onprem-ew4-1 - ? module.landing-to-onprem-secondary-vpn[0].google_compute_router_peer.bgp_peer["0"] - : advertise_mode: DEFAULT + module.landing-to-onprem-secondary-vpn[0].google_compute_router_peer.bgp_peer["0"]: + advertise_mode: DEFAULT advertised_groups: [] advertised_ip_ranges: [] advertised_route_priority: 1000 @@ -1831,8 +1827,8 @@ values: router: vpn-vpn-to-onprem-ew4 router_appliance_instance: null timeouts: null - ? module.landing-to-onprem-secondary-vpn[0].google_compute_router_peer.bgp_peer["1"] - : advertise_mode: DEFAULT + module.landing-to-onprem-secondary-vpn[0].google_compute_router_peer.bgp_peer["1"]: + advertise_mode: DEFAULT advertised_groups: [] advertised_ip_ranges: [] advertised_route_priority: 1000 @@ -1848,8 +1844,8 @@ values: router: vpn-vpn-to-onprem-ew4 router_appliance_instance: null timeouts: null - ? module.landing-to-onprem-secondary-vpn[0].google_compute_vpn_tunnel.tunnels["0"] - : description: null + module.landing-to-onprem-secondary-vpn[0].google_compute_vpn_tunnel.tunnels["0"]: + description: null ike_version: 2 labels: null name: vpn-to-onprem-ew4-0 @@ -1862,8 +1858,8 @@ values: target_vpn_gateway: null timeouts: null vpn_gateway_interface: 0 - ? module.landing-to-onprem-secondary-vpn[0].google_compute_vpn_tunnel.tunnels["1"] - : description: null + module.landing-to-onprem-secondary-vpn[0].google_compute_vpn_tunnel.tunnels["1"]: + description: null ike_version: 2 labels: null name: vpn-to-onprem-ew4-1 @@ -1917,8 +1913,8 @@ values: project: fast2-prod-net-landing-0 tags: null timeouts: null - ? module.landing-vpc.google_compute_subnetwork.subnetwork["europe-west1/landing-default"] - : description: Default europe-west1 subnet for landing + module.landing-vpc.google_compute_subnetwork.subnetwork["europe-west1/landing-default"]: + description: Default europe-west1 subnet for landing ip_cidr_range: 10.64.0.0/24 ipv6_access_type: null log_config: [] @@ -1930,8 +1926,8 @@ values: role: null secondary_ip_range: [] timeouts: null - ? module.landing-vpc.google_compute_subnetwork.subnetwork["europe-west4/landing-default"] - : description: Default europe-west4 subnet for landing + module.landing-vpc.google_compute_subnetwork.subnetwork["europe-west4/landing-default"]: + description: Default europe-west4 subnet for landing ip_cidr_range: 10.80.0.0/24 ipv6_access_type: null log_config: [] @@ -1950,7 +1946,7 @@ values: enable_logging: null name: prod-landing-0 networks: - - {} + - {} project: fast2-prod-net-landing-0 timeouts: null module.nva["primary-b"].google_compute_instance.default[0]: @@ -1958,15 +1954,15 @@ values: allow_stopping_for_update: true attached_disk: [] boot_disk: - - auto_delete: true - disk_encryption_key_raw: null - initialize_params: - - enable_confidential_compute: null - image: projects/cos-cloud/global/images/family/cos-stable - resource_manager_tags: null - size: 10 - type: pd-balanced - mode: READ_WRITE + - auto_delete: true + disk_encryption_key_raw: null + initialize_params: + - enable_confidential_compute: null + image: projects/cos-cloud/global/images/family/cos-stable + resource_manager_tags: null + size: 10 + type: pd-balanced + mode: READ_WRITE can_ip_forward: true deletion_protection: false description: Managed by the compute-vm Terraform module. @@ -1976,8 +1972,7 @@ values: labels: null machine_type: e2-standard-2 metadata: - user-data: - "#cloud-config\n\n# Copyright 2023 Google LLC\n#\n# Licensed under\ + user-data: "#cloud-config\n\n# Copyright 2024 Google LLC\n#\n# Licensed under\ \ the Apache License, Version 2.0 (the \"License\");\n# you may not use this\ \ file except in compliance with the License.\n# You may obtain a copy of\ \ the License at\n#\n# https://www.apache.org/licenses/LICENSE-2.0\n#\n\ @@ -2148,7 +2143,9 @@ values: \ permissions: 0744\n owner: root\n content: |\n iptables --policy\ \ FORWARD ACCEPT\n /var/run/nva/policy_based_routing.sh eth0 &>/dev/null\ \ &\n iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE\n ip\ - \ route add 10.64.127.0/17 via `curl http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/gateway\ + \ route add 35.235.240.0/20 via `curl http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/gateway\ + \ -H \"Metadata-Flavor:Google\"` dev eth0\n ip route add 10.64.127.0/17\ + \ via `curl http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/gateway\ \ -H \"Metadata-Flavor:Google\"` dev eth0\n ip route add 10.80.127.0/17\ \ via `curl http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/gateway\ \ -H \"Metadata-Flavor:Google\"` dev eth0\n /var/run/nva/policy_based_routing.sh\ @@ -2162,44 +2159,44 @@ values: metadata_startup_script: null name: nva-ew1-b network_interface: - - access_config: [] - alias_ip_range: [] - ipv6_access_config: [] - network_ip: 10.64.128.101 - nic_type: null - queue_count: null - security_policy: null - - access_config: [] - alias_ip_range: [] - ipv6_access_config: [] - network_ip: 10.64.0.101 - nic_type: null - queue_count: null - security_policy: null + - access_config: [] + alias_ip_range: [] + ipv6_access_config: [] + network_ip: 10.64.128.101 + nic_type: null + queue_count: null + security_policy: null + - access_config: [] + alias_ip_range: [] + ipv6_access_config: [] + network_ip: 10.64.0.101 + nic_type: null + queue_count: null + security_policy: null network_performance_config: [] params: [] project: fast2-prod-net-landing-0 resource_policies: null scheduling: - - automatic_restart: true - instance_termination_action: null - local_ssd_recovery_timeout: [] - maintenance_interval: null - max_run_duration: [] - min_node_cpus: null - node_affinities: [] - on_host_maintenance: MIGRATE - preemptible: false - provisioning_model: STANDARD + - automatic_restart: true + instance_termination_action: null + local_ssd_recovery_timeout: [] + maintenance_interval: null + max_run_duration: [] + min_node_cpus: null + node_affinities: [] + on_host_maintenance: MIGRATE + preemptible: false + provisioning_model: STANDARD scratch_disk: [] service_account: - - scopes: - - https://www.googleapis.com/auth/devstorage.read_only - - https://www.googleapis.com/auth/logging.write - - https://www.googleapis.com/auth/monitoring.write + - scopes: + - https://www.googleapis.com/auth/devstorage.read_only + - https://www.googleapis.com/auth/logging.write + - https://www.googleapis.com/auth/monitoring.write shielded_instance_config: [] tags: - - nva + - nva timeouts: null zone: europe-west1-b module.nva["primary-c"].google_compute_instance.default[0]: @@ -2207,15 +2204,15 @@ values: allow_stopping_for_update: true attached_disk: [] boot_disk: - - auto_delete: true - disk_encryption_key_raw: null - initialize_params: - - enable_confidential_compute: null - image: projects/cos-cloud/global/images/family/cos-stable - resource_manager_tags: null - size: 10 - type: pd-balanced - mode: READ_WRITE + - auto_delete: true + disk_encryption_key_raw: null + initialize_params: + - enable_confidential_compute: null + image: projects/cos-cloud/global/images/family/cos-stable + resource_manager_tags: null + size: 10 + type: pd-balanced + mode: READ_WRITE can_ip_forward: true deletion_protection: false description: Managed by the compute-vm Terraform module. @@ -2225,8 +2222,7 @@ values: labels: null machine_type: e2-standard-2 metadata: - user-data: - "#cloud-config\n\n# Copyright 2023 Google LLC\n#\n# Licensed under\ + user-data: "#cloud-config\n\n# Copyright 2024 Google LLC\n#\n# Licensed under\ \ the Apache License, Version 2.0 (the \"License\");\n# you may not use this\ \ file except in compliance with the License.\n# You may obtain a copy of\ \ the License at\n#\n# https://www.apache.org/licenses/LICENSE-2.0\n#\n\ @@ -2397,7 +2393,9 @@ values: \ permissions: 0744\n owner: root\n content: |\n iptables --policy\ \ FORWARD ACCEPT\n /var/run/nva/policy_based_routing.sh eth0 &>/dev/null\ \ &\n iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE\n ip\ - \ route add 10.64.127.0/17 via `curl http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/gateway\ + \ route add 35.235.240.0/20 via `curl http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/gateway\ + \ -H \"Metadata-Flavor:Google\"` dev eth0\n ip route add 10.64.127.0/17\ + \ via `curl http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/gateway\ \ -H \"Metadata-Flavor:Google\"` dev eth0\n ip route add 10.80.127.0/17\ \ via `curl http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/gateway\ \ -H \"Metadata-Flavor:Google\"` dev eth0\n /var/run/nva/policy_based_routing.sh\ @@ -2411,44 +2409,44 @@ values: metadata_startup_script: null name: nva-ew1-c network_interface: - - access_config: [] - alias_ip_range: [] - ipv6_access_config: [] - network_ip: 10.64.128.102 - nic_type: null - queue_count: null - security_policy: null - - access_config: [] - alias_ip_range: [] - ipv6_access_config: [] - network_ip: 10.64.0.102 - nic_type: null - queue_count: null - security_policy: null + - access_config: [] + alias_ip_range: [] + ipv6_access_config: [] + network_ip: 10.64.128.102 + nic_type: null + queue_count: null + security_policy: null + - access_config: [] + alias_ip_range: [] + ipv6_access_config: [] + network_ip: 10.64.0.102 + nic_type: null + queue_count: null + security_policy: null network_performance_config: [] params: [] project: fast2-prod-net-landing-0 resource_policies: null scheduling: - - automatic_restart: true - instance_termination_action: null - local_ssd_recovery_timeout: [] - maintenance_interval: null - max_run_duration: [] - min_node_cpus: null - node_affinities: [] - on_host_maintenance: MIGRATE - preemptible: false - provisioning_model: STANDARD + - automatic_restart: true + instance_termination_action: null + local_ssd_recovery_timeout: [] + maintenance_interval: null + max_run_duration: [] + min_node_cpus: null + node_affinities: [] + on_host_maintenance: MIGRATE + preemptible: false + provisioning_model: STANDARD scratch_disk: [] service_account: - - scopes: - - https://www.googleapis.com/auth/devstorage.read_only - - https://www.googleapis.com/auth/logging.write - - https://www.googleapis.com/auth/monitoring.write + - scopes: + - https://www.googleapis.com/auth/devstorage.read_only + - https://www.googleapis.com/auth/logging.write + - https://www.googleapis.com/auth/monitoring.write shielded_instance_config: [] tags: - - nva + - nva timeouts: null zone: europe-west1-c module.nva["secondary-b"].google_compute_instance.default[0]: @@ -2456,15 +2454,15 @@ values: allow_stopping_for_update: true attached_disk: [] boot_disk: - - auto_delete: true - disk_encryption_key_raw: null - initialize_params: - - enable_confidential_compute: null - image: projects/cos-cloud/global/images/family/cos-stable - resource_manager_tags: null - size: 10 - type: pd-balanced - mode: READ_WRITE + - auto_delete: true + disk_encryption_key_raw: null + initialize_params: + - enable_confidential_compute: null + image: projects/cos-cloud/global/images/family/cos-stable + resource_manager_tags: null + size: 10 + type: pd-balanced + mode: READ_WRITE can_ip_forward: true deletion_protection: false description: Managed by the compute-vm Terraform module. @@ -2474,8 +2472,7 @@ values: labels: null machine_type: e2-standard-2 metadata: - user-data: - "#cloud-config\n\n# Copyright 2023 Google LLC\n#\n# Licensed under\ + user-data: "#cloud-config\n\n# Copyright 2024 Google LLC\n#\n# Licensed under\ \ the Apache License, Version 2.0 (the \"License\");\n# you may not use this\ \ file except in compliance with the License.\n# You may obtain a copy of\ \ the License at\n#\n# https://www.apache.org/licenses/LICENSE-2.0\n#\n\ @@ -2646,7 +2643,9 @@ values: \ permissions: 0744\n owner: root\n content: |\n iptables --policy\ \ FORWARD ACCEPT\n /var/run/nva/policy_based_routing.sh eth0 &>/dev/null\ \ &\n iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE\n ip\ - \ route add 10.64.127.0/17 via `curl http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/gateway\ + \ route add 35.235.240.0/20 via `curl http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/gateway\ + \ -H \"Metadata-Flavor:Google\"` dev eth0\n ip route add 10.64.127.0/17\ + \ via `curl http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/gateway\ \ -H \"Metadata-Flavor:Google\"` dev eth0\n ip route add 10.80.127.0/17\ \ via `curl http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/gateway\ \ -H \"Metadata-Flavor:Google\"` dev eth0\n /var/run/nva/policy_based_routing.sh\ @@ -2660,44 +2659,44 @@ values: metadata_startup_script: null name: nva-ew4-b network_interface: - - access_config: [] - alias_ip_range: [] - ipv6_access_config: [] - network_ip: 10.80.128.101 - nic_type: null - queue_count: null - security_policy: null - - access_config: [] - alias_ip_range: [] - ipv6_access_config: [] - network_ip: 10.80.0.101 - nic_type: null - queue_count: null - security_policy: null + - access_config: [] + alias_ip_range: [] + ipv6_access_config: [] + network_ip: 10.80.128.101 + nic_type: null + queue_count: null + security_policy: null + - access_config: [] + alias_ip_range: [] + ipv6_access_config: [] + network_ip: 10.80.0.101 + nic_type: null + queue_count: null + security_policy: null network_performance_config: [] params: [] project: fast2-prod-net-landing-0 resource_policies: null scheduling: - - automatic_restart: true - instance_termination_action: null - local_ssd_recovery_timeout: [] - maintenance_interval: null - max_run_duration: [] - min_node_cpus: null - node_affinities: [] - on_host_maintenance: MIGRATE - preemptible: false - provisioning_model: STANDARD + - automatic_restart: true + instance_termination_action: null + local_ssd_recovery_timeout: [] + maintenance_interval: null + max_run_duration: [] + min_node_cpus: null + node_affinities: [] + on_host_maintenance: MIGRATE + preemptible: false + provisioning_model: STANDARD scratch_disk: [] service_account: - - scopes: - - https://www.googleapis.com/auth/devstorage.read_only - - https://www.googleapis.com/auth/logging.write - - https://www.googleapis.com/auth/monitoring.write + - scopes: + - https://www.googleapis.com/auth/devstorage.read_only + - https://www.googleapis.com/auth/logging.write + - https://www.googleapis.com/auth/monitoring.write shielded_instance_config: [] tags: - - nva + - nva timeouts: null zone: europe-west4-b module.nva["secondary-c"].google_compute_instance.default[0]: @@ -2705,15 +2704,15 @@ values: allow_stopping_for_update: true attached_disk: [] boot_disk: - - auto_delete: true - disk_encryption_key_raw: null - initialize_params: - - enable_confidential_compute: null - image: projects/cos-cloud/global/images/family/cos-stable - resource_manager_tags: null - size: 10 - type: pd-balanced - mode: READ_WRITE + - auto_delete: true + disk_encryption_key_raw: null + initialize_params: + - enable_confidential_compute: null + image: projects/cos-cloud/global/images/family/cos-stable + resource_manager_tags: null + size: 10 + type: pd-balanced + mode: READ_WRITE can_ip_forward: true deletion_protection: false description: Managed by the compute-vm Terraform module. @@ -2723,8 +2722,7 @@ values: labels: null machine_type: e2-standard-2 metadata: - user-data: - "#cloud-config\n\n# Copyright 2023 Google LLC\n#\n# Licensed under\ + user-data: "#cloud-config\n\n# Copyright 2024 Google LLC\n#\n# Licensed under\ \ the Apache License, Version 2.0 (the \"License\");\n# you may not use this\ \ file except in compliance with the License.\n# You may obtain a copy of\ \ the License at\n#\n# https://www.apache.org/licenses/LICENSE-2.0\n#\n\ @@ -2895,7 +2893,9 @@ values: \ permissions: 0744\n owner: root\n content: |\n iptables --policy\ \ FORWARD ACCEPT\n /var/run/nva/policy_based_routing.sh eth0 &>/dev/null\ \ &\n iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE\n ip\ - \ route add 10.64.127.0/17 via `curl http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/gateway\ + \ route add 35.235.240.0/20 via `curl http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/gateway\ + \ -H \"Metadata-Flavor:Google\"` dev eth0\n ip route add 10.64.127.0/17\ + \ via `curl http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/gateway\ \ -H \"Metadata-Flavor:Google\"` dev eth0\n ip route add 10.80.127.0/17\ \ via `curl http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/gateway\ \ -H \"Metadata-Flavor:Google\"` dev eth0\n /var/run/nva/policy_based_routing.sh\ @@ -2909,44 +2909,44 @@ values: metadata_startup_script: null name: nva-ew4-c network_interface: - - access_config: [] - alias_ip_range: [] - ipv6_access_config: [] - network_ip: 10.80.128.102 - nic_type: null - queue_count: null - security_policy: null - - access_config: [] - alias_ip_range: [] - ipv6_access_config: [] - network_ip: 10.80.0.102 - nic_type: null - queue_count: null - security_policy: null + - access_config: [] + alias_ip_range: [] + ipv6_access_config: [] + network_ip: 10.80.128.102 + nic_type: null + queue_count: null + security_policy: null + - access_config: [] + alias_ip_range: [] + ipv6_access_config: [] + network_ip: 10.80.0.102 + nic_type: null + queue_count: null + security_policy: null network_performance_config: [] params: [] project: fast2-prod-net-landing-0 resource_policies: null scheduling: - - automatic_restart: true - instance_termination_action: null - local_ssd_recovery_timeout: [] - maintenance_interval: null - max_run_duration: [] - min_node_cpus: null - node_affinities: [] - on_host_maintenance: MIGRATE - preemptible: false - provisioning_model: STANDARD + - automatic_restart: true + instance_termination_action: null + local_ssd_recovery_timeout: [] + maintenance_interval: null + max_run_duration: [] + min_node_cpus: null + node_affinities: [] + on_host_maintenance: MIGRATE + preemptible: false + provisioning_model: STANDARD scratch_disk: [] service_account: - - scopes: - - https://www.googleapis.com/auth/devstorage.read_only - - https://www.googleapis.com/auth/logging.write - - https://www.googleapis.com/auth/monitoring.write + - scopes: + - https://www.googleapis.com/auth/devstorage.read_only + - https://www.googleapis.com/auth/logging.write + - https://www.googleapis.com/auth/monitoring.write shielded_instance_config: [] tags: - - nva + - nva timeouts: null zone: europe-west4-c module.peering-dev.google_compute_network_peering.local_network_peering: @@ -2977,9 +2977,9 @@ values: import_subnet_routes_with_public_ip: null stack_type: IPV4_ONLY timeouts: null - ? module.prod-dns-peer-landing-rev-10.google_dns_managed_zone.dns_managed_zone[0] - : cloud_logging_config: - - enable_logging: false + module.prod-dns-peer-landing-rev-10.google_dns_managed_zone.dns_managed_zone[0]: + cloud_logging_config: + - enable_logging: false description: Terraform managed. dns_name: 10.in-addr.arpa. dnssec_config: [] @@ -2994,7 +2994,7 @@ values: visibility: private module.prod-dns-peer-landing-root.google_dns_managed_zone.dns_managed_zone[0]: cloud_logging_config: - - enable_logging: false + - enable_logging: false description: Terraform managed. dns_name: . dnssec_config: [] @@ -3009,7 +3009,7 @@ values: visibility: private module.prod-dns-private-zone.google_dns_managed_zone.dns_managed_zone[0]: cloud_logging_config: - - enable_logging: false + - enable_logging: false description: Terraform managed. dns_name: prod.gcp.example.com. dnssec_config: [] @@ -3022,41 +3022,41 @@ values: service_directory_config: [] timeouts: null visibility: private - ? module.prod-dns-private-zone.google_dns_record_set.dns_record_set["A localhost"] - : managed_zone: prod-gcp-example-com + module.prod-dns-private-zone.google_dns_record_set.dns_record_set["A localhost"]: + managed_zone: prod-gcp-example-com name: localhost.prod.gcp.example.com. project: fast2-prod-net-spoke-0 routing_policy: [] rrdatas: - - 127.0.0.1 + - 127.0.0.1 ttl: 300 type: A - ? module.prod-spoke-firewall.google_compute_firewall.custom-rules["ingress-default-deny"] - : allow: [] + module.prod-spoke-firewall.google_compute_firewall.custom-rules["ingress-default-deny"]: + allow: [] deny: - - ports: [] - protocol: all + - ports: [] + protocol: all description: Deny and log any unmatched ingress traffic. direction: INGRESS disabled: false log_config: - - metadata: EXCLUDE_ALL_METADATA + - metadata: EXCLUDE_ALL_METADATA name: ingress-default-deny network: prod-spoke-0 priority: 65535 project: fast2-prod-net-spoke-0 source_ranges: - - 0.0.0.0/0 + - 0.0.0.0/0 source_service_accounts: null source_tags: null target_service_accounts: null target_tags: null timeouts: null - ? module.prod-spoke-project.google_compute_shared_vpc_host_project.shared_vpc_host[0] - : project: fast2-prod-net-spoke-0 + module.prod-spoke-project.google_compute_shared_vpc_host_project.shared_vpc_host[0]: + project: fast2-prod-net-spoke-0 timeouts: null - ? module.prod-spoke-project.google_monitoring_monitored_project.primary["fast2-prod-net-landing-0"] - : metrics_scope: fast2-prod-net-landing-0 + module.prod-spoke-project.google_monitoring_monitored_project.primary["fast2-prod-net-landing-0"]: + metrics_scope: fast2-prod-net-landing-0 name: fast2-prod-net-spoke-0 timeouts: null module.prod-spoke-project.google_project.project[0]: @@ -3069,73 +3069,73 @@ values: project_id: fast2-prod-net-spoke-0 skip_delete: false timeouts: null - ? module.prod-spoke-project.google_project_iam_binding.authoritative["roles/dns.admin"] - : condition: [] + module.prod-spoke-project.google_project_iam_binding.authoritative["roles/dns.admin"]: + condition: [] members: - - serviceAccount:string + - serviceAccount:string project: fast2-prod-net-spoke-0 role: roles/dns.admin - ? module.prod-spoke-project.google_project_iam_binding.bindings["sa_delegated_grants"] - : condition: - - description: Production host project delegated grants. - expression: api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly(['roles/composer.sharedVpcAgent','roles/compute.networkUser','roles/compute.networkViewer','roles/container.hostServiceAgentUser','roles/multiclusterservicediscovery.serviceAgent','roles/vpcaccess.user']) - title: prod_stage3_sa_delegated_grants + module.prod-spoke-project.google_project_iam_binding.bindings["sa_delegated_grants"]: + condition: + - description: Production host project delegated grants. + expression: api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly(['roles/composer.sharedVpcAgent','roles/compute.networkUser','roles/compute.networkViewer','roles/container.hostServiceAgentUser','roles/multiclusterservicediscovery.serviceAgent','roles/vpcaccess.user']) + title: prod_stage3_sa_delegated_grants members: - - serviceAccount:string + - serviceAccount:string project: fast2-prod-net-spoke-0 role: roles/resourcemanager.projectIamAdmin module.prod-spoke-project.google_project_iam_member.servicenetworking[0]: condition: [] project: fast2-prod-net-spoke-0 role: roles/servicenetworking.serviceAgent - ? module.prod-spoke-project.google_project_service.project_services["compute.googleapis.com"] - : disable_dependent_services: false + module.prod-spoke-project.google_project_service.project_services["compute.googleapis.com"]: + disable_dependent_services: false disable_on_destroy: false project: fast2-prod-net-spoke-0 service: compute.googleapis.com timeouts: null - ? module.prod-spoke-project.google_project_service.project_services["dns.googleapis.com"] - : disable_dependent_services: false + module.prod-spoke-project.google_project_service.project_services["dns.googleapis.com"]: + disable_dependent_services: false disable_on_destroy: false project: fast2-prod-net-spoke-0 service: dns.googleapis.com timeouts: null - ? module.prod-spoke-project.google_project_service.project_services["iap.googleapis.com"] - : disable_dependent_services: false + module.prod-spoke-project.google_project_service.project_services["iap.googleapis.com"]: + disable_dependent_services: false disable_on_destroy: false project: fast2-prod-net-spoke-0 service: iap.googleapis.com timeouts: null - ? module.prod-spoke-project.google_project_service.project_services["networkmanagement.googleapis.com"] - : disable_dependent_services: false + module.prod-spoke-project.google_project_service.project_services["networkmanagement.googleapis.com"]: + disable_dependent_services: false disable_on_destroy: false project: fast2-prod-net-spoke-0 service: networkmanagement.googleapis.com timeouts: null - ? module.prod-spoke-project.google_project_service.project_services["servicenetworking.googleapis.com"] - : disable_dependent_services: false + module.prod-spoke-project.google_project_service.project_services["servicenetworking.googleapis.com"]: + disable_dependent_services: false disable_on_destroy: false project: fast2-prod-net-spoke-0 service: servicenetworking.googleapis.com timeouts: null - ? module.prod-spoke-project.google_project_service.project_services["stackdriver.googleapis.com"] - : disable_dependent_services: false + module.prod-spoke-project.google_project_service.project_services["stackdriver.googleapis.com"]: + disable_dependent_services: false disable_on_destroy: false project: fast2-prod-net-spoke-0 service: stackdriver.googleapis.com timeouts: null - ? module.prod-spoke-project.google_project_service.project_services["vpcaccess.googleapis.com"] - : disable_dependent_services: false + module.prod-spoke-project.google_project_service.project_services["vpcaccess.googleapis.com"]: + disable_dependent_services: false disable_on_destroy: false project: fast2-prod-net-spoke-0 service: vpcaccess.googleapis.com timeouts: null - ? module.prod-spoke-project.google_project_service_identity.jit_si["iap.googleapis.com"] - : project: fast2-prod-net-spoke-0 + module.prod-spoke-project.google_project_service_identity.jit_si["iap.googleapis.com"]: + project: fast2-prod-net-spoke-0 service: iap.googleapis.com timeouts: null - ? module.prod-spoke-project.google_project_service_identity.servicenetworking[0] - : project: fast2-prod-net-spoke-0 + module.prod-spoke-project.google_project_service_identity.servicenetworking[0]: + project: fast2-prod-net-spoke-0 service: servicenetworking.googleapis.com timeouts: null module.prod-spoke-vpc.google_compute_network.network[0]: @@ -3175,8 +3175,8 @@ values: project: fast2-prod-net-spoke-0 tags: null timeouts: null - ? module.prod-spoke-vpc.google_compute_subnetwork.subnetwork["europe-west1/prod-default"] - : description: Default europe-west1 subnet for prod + module.prod-spoke-vpc.google_compute_subnetwork.subnetwork["europe-west1/prod-default"]: + description: Default europe-west1 subnet for prod ip_cidr_range: 10.72.0.0/24 ipv6_access_type: null log_config: [] @@ -3188,8 +3188,8 @@ values: role: null secondary_ip_range: [] timeouts: null - ? module.prod-spoke-vpc.google_compute_subnetwork.subnetwork["europe-west4/prod-default"] - : description: Default europe-west4 subnet for prod + module.prod-spoke-vpc.google_compute_subnetwork.subnetwork["europe-west4/prod-default"]: + description: Default europe-west4 subnet for prod ip_cidr_range: 10.88.0.0/24 ipv6_access_type: null log_config: [] @@ -3208,18 +3208,18 @@ values: enable_logging: true name: prod-spoke-0 networks: - - {} + - {} project: fast2-prod-net-spoke-0 timeouts: null module.spokes-dmz["primary"].google_compute_router.cr: bgp: - - advertise_mode: CUSTOM - advertised_groups: [] - advertised_ip_ranges: - - description: Default route. - range: 0.0.0.0/0 - asn: 64512 - keepalive_interval: 20 + - advertise_mode: CUSTOM + advertised_groups: [] + advertised_ip_ranges: + - description: Default route. + range: 0.0.0.0/0 + asn: 64512 + keepalive_interval: 20 description: null encrypted_interconnect_router: null name: prod-spoke-dmz-ew1-cr @@ -3306,10 +3306,10 @@ values: labels: null linked_interconnect_attachments: [] linked_router_appliance_instances: - - instances: - - {} - - {} - site_to_site_data_transfer: false + - instances: + - {} + - {} + site_to_site_data_transfer: false linked_vpc_network: [] linked_vpn_tunnels: [] location: europe-west1 @@ -3318,13 +3318,13 @@ values: timeouts: null module.spokes-dmz["secondary"].google_compute_router.cr: bgp: - - advertise_mode: CUSTOM - advertised_groups: [] - advertised_ip_ranges: - - description: Default route. - range: 0.0.0.0/0 - asn: 64512 - keepalive_interval: 20 + - advertise_mode: CUSTOM + advertised_groups: [] + advertised_ip_ranges: + - description: Default route. + range: 0.0.0.0/0 + asn: 64512 + keepalive_interval: 20 description: null encrypted_interconnect_router: null name: prod-spoke-dmz-ew4-cr @@ -3411,10 +3411,10 @@ values: labels: null linked_interconnect_attachments: [] linked_router_appliance_instances: - - instances: - - {} - - {} - site_to_site_data_transfer: false + - instances: + - {} + - {} + site_to_site_data_transfer: false linked_vpc_network: [] linked_vpn_tunnels: [] location: europe-west4 @@ -3423,23 +3423,23 @@ values: timeouts: null module.spokes-landing["primary"].google_compute_router.cr: bgp: - - advertise_mode: CUSTOM - advertised_groups: [] - advertised_ip_ranges: - - description: GCP landing primary. - range: 10.64.0.0/17 - - description: GCP dev primary. - range: 10.68.0.0/16 - - description: GCP prod primary. - range: 10.72.0.0/16 - - description: GCP landing secondary. - range: 10.80.0.0/17 - - description: GCP dev secondary. - range: 10.84.0.0/16 - - description: GCP prod secondary. - range: 10.88.0.0/16 - asn: 64515 - keepalive_interval: 20 + - advertise_mode: CUSTOM + advertised_groups: [] + advertised_ip_ranges: + - description: GCP landing primary. + range: 10.64.0.0/17 + - description: GCP dev primary. + range: 10.68.0.0/16 + - description: GCP prod primary. + range: 10.72.0.0/16 + - description: GCP landing secondary. + range: 10.80.0.0/17 + - description: GCP dev secondary. + range: 10.84.0.0/16 + - description: GCP prod secondary. + range: 10.88.0.0/16 + asn: 64515 + keepalive_interval: 20 description: null encrypted_interconnect_router: null name: prod-spoke-landing-ew1-cr @@ -3526,10 +3526,10 @@ values: labels: null linked_interconnect_attachments: [] linked_router_appliance_instances: - - instances: - - {} - - {} - site_to_site_data_transfer: false + - instances: + - {} + - {} + site_to_site_data_transfer: false linked_vpc_network: [] linked_vpn_tunnels: [] location: europe-west1 @@ -3538,23 +3538,23 @@ values: timeouts: null module.spokes-landing["secondary"].google_compute_router.cr: bgp: - - advertise_mode: CUSTOM - advertised_groups: [] - advertised_ip_ranges: - - description: GCP landing primary. - range: 10.64.0.0/17 - - description: GCP dev primary. - range: 10.68.0.0/16 - - description: GCP prod primary. - range: 10.72.0.0/16 - - description: GCP landing secondary. - range: 10.80.0.0/17 - - description: GCP dev secondary. - range: 10.84.0.0/16 - - description: GCP prod secondary. - range: 10.88.0.0/16 - asn: 64515 - keepalive_interval: 20 + - advertise_mode: CUSTOM + advertised_groups: [] + advertised_ip_ranges: + - description: GCP landing primary. + range: 10.64.0.0/17 + - description: GCP dev primary. + range: 10.68.0.0/16 + - description: GCP prod primary. + range: 10.72.0.0/16 + - description: GCP landing secondary. + range: 10.80.0.0/17 + - description: GCP dev secondary. + range: 10.84.0.0/16 + - description: GCP prod secondary. + range: 10.88.0.0/16 + asn: 64515 + keepalive_interval: 20 description: null encrypted_interconnect_router: null name: prod-spoke-landing-ew4-cr @@ -3641,10 +3641,10 @@ values: labels: null linked_interconnect_attachments: [] linked_router_appliance_instances: - - instances: - - {} - - {} - site_to_site_data_transfer: false + - instances: + - {} + - {} + site_to_site_data_transfer: false linked_vpc_network: [] linked_vpn_tunnels: [] location: europe-west4 @@ -3702,3 +3702,4 @@ outputs: shared_vpc_self_links: __missing__ tfvars: __missing__ vpn_gateway_endpoints: __missing__ + From 01533a4a66da722574991229c4803bf426dd9908 Mon Sep 17 00:00:00 2001 From: Ludo Date: Fri, 10 May 2024 07:56:11 +0200 Subject: [PATCH 19/31] update changelog --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac785d99a9..6c2177cdbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ All notable changes to this project will be documented in this file. ### BLUEPRINTS +- [[#2243](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2243)] Added new attributes Apigee organization and bumped up providers version ([apichick](https://github.com/apichick)) +- [[#2239](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2239)] Update README.md ([vicenteg](https://github.com/vicenteg)) +- [[#2230](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2230)] docs: :memo: fix error in phpIPAM terraform config by updating VPC pe… ([PapaPeskwo](https://github.com/PapaPeskwo)) - [[#2227](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2227)] Bump golang.org/x/net from 0.17.0 to 0.23.0 in /blueprints/cloud-operations/unmanaged-instances-healthcheck/function/healthchecker ([dependabot[bot]](https://github.com/dependabot[bot])) - [[#2228](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2228)] Bump golang.org/x/net from 0.17.0 to 0.23.0 in /blueprints/cloud-operations/unmanaged-instances-healthcheck/function/restarter ([dependabot[bot]](https://github.com/dependabot[bot])) - [[#2226](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2226)] fix cloud sql PSA after module upgrade ([simonebruzzechesse](https://github.com/simonebruzzechesse)) @@ -29,6 +32,10 @@ All notable changes to this project will be documented in this file. ### FAST +- [[#2260](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2260)] Remove data source from folder module ([ludoo](https://github.com/ludoo)) +- [[#2253](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2253)] Misc FAST fixes ([juliocc](https://github.com/juliocc)) +- [[#2235](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2235)] Update FAST logging ([juliocc](https://github.com/juliocc)) +- [[#2233](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2233)] Fix permissions for branch network dev - read sa ([LucaPrete](https://github.com/LucaPrete)) - [[#2221](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2221)] Enable TFLint in FAST stages ([juliocc](https://github.com/juliocc)) - [[#2220](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2220)] Add tflint to pipelines ([juliocc](https://github.com/juliocc)) - [[#2218](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2218)] **incompatible change:** Allow multiple PSA service providers in net-vpc module ([ludoo](https://github.com/ludoo)) @@ -46,6 +53,19 @@ All notable changes to this project will be documented in this file. ### MODULES +- [[#2262](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2262)] Make Simple NVA route IAP traffic through NIC 0 ([juliocc](https://github.com/juliocc)) +- [[#2261](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2261)] Add Hybrid NAT support ([juliocc](https://github.com/juliocc)) +- [[#2260](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2260)] Remove data source from folder module ([ludoo](https://github.com/ludoo)) +- [[#2247](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2247)] Fix workstation-cluster module for private deployment ([simonebruzzechesse](https://github.com/simonebruzzechesse)) +- [[#2252](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2252)] Add support for labels to GKE backup plans ([ludoo](https://github.com/ludoo)) +- [[#2251](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2251)] Fix factory ingress policy services in vpc-sc module ([ludoo](https://github.com/ludoo)) +- [[#2248](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2248)] Added missing identity when connectors API is enabled ([jnahelou](https://github.com/jnahelou)) +- [[#2246](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2246)] Fixed issue with service networking DNS peering ([apichick](https://github.com/apichick)) +- [[#2243](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2243)] Added new attributes Apigee organization and bumped up providers version ([apichick](https://github.com/apichick)) +- [[#2244](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2244)] **incompatible change:** Removed BFD settings from net-vpn-ha module as it is not supported ([apichick](https://github.com/apichick)) +- [[#2241](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2241)] Use default labels on pubsub subscription when no override is provided ([wiktorn](https://github.com/wiktorn)) +- [[#2238](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2238)] fix: allow disabling node autoprovisioning ([kumadee](https://github.com/kumadee)) +- [[#2234](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2234)] Added build environment variables in cloud function v1 ([luigi-bitonti](https://github.com/luigi-bitonti)) - [[#2229](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2229)] **incompatible change:** Refactor vpc-sc support in project module, add support for dry run ([ludoo](https://github.com/ludoo)) - [[#2226](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2226)] fix cloud sql PSA after module upgrade ([simonebruzzechesse](https://github.com/simonebruzzechesse)) - [[#2224](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2224)] added missing option for exclusion scope ([cmalpe](https://github.com/cmalpe)) From 2b6c81f73d1851068e1eab027130c1457ec2a170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Niesiob=C4=99dzki?= Date: Fri, 10 May 2024 09:54:19 +0200 Subject: [PATCH 20/31] Update docs - gcp-network-admins -> gcp-vpc-network-admins --- fast/stages/0-bootstrap/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fast/stages/0-bootstrap/README.md b/fast/stages/0-bootstrap/README.md index 5d456302f5..758eac468f 100644 --- a/fast/stages/0-bootstrap/README.md +++ b/fast/stages/0-bootstrap/README.md @@ -276,7 +276,7 @@ Before the first run, the following IAM groups must exist to allow IAM bindings - `gcp-billing-admins` - `gcp-devops` -- `gcp-network-admins` +- `gcp-vpc-network-admins` - `gcp-organization-admins` - `gcp-security-admins` From 5b3ed10cdadbd7f082b8b13b94c297de5c2ee536 Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Fri, 10 May 2024 16:19:35 -0600 Subject: [PATCH 21/31] Fix bug from output typo in new project-factory module (#2264) `local.folders` is just a map of var-based keys to string manipulations on those keys, while `local.hierarchy` is the seemingly-intended map of var-based keys to generated IDs/numbers. see https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/blob/master/modules/project-factory/factory-folders.tf#L32 vs. https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/blob/master/modules/project-factory/factory-folders.tf#L39 Thank you for recently developing this convenient module! --- modules/project-factory/outputs.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/project-factory/outputs.tf b/modules/project-factory/outputs.tf index 9f49ebe986..5040e76671 100644 --- a/modules/project-factory/outputs.tf +++ b/modules/project-factory/outputs.tf @@ -16,7 +16,7 @@ output "folders" { description = "Folder ids." - value = local.folders + value = local.hierarchy } output "projects" { From 35a17a46ba99b4845fcc9a7ea6b140c254ea142e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Niesiob=C4=99dzki?= Date: Sat, 11 May 2024 15:19:09 +0000 Subject: [PATCH 22/31] Fix failing E2E tests --- modules/net-vpc/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/net-vpc/README.md b/modules/net-vpc/README.md index d29251cc96..fb115804c2 100644 --- a/modules/net-vpc/README.md +++ b/modules/net-vpc/README.md @@ -299,10 +299,12 @@ module "vpc" { { ranges = { myrange = "10.0.1.0/24" } # service_producer = "servicenetworking.googleapis.com" # default value + deletion_policy = "ABANDON" }, { ranges = { netapp = "10.0.2.0/24" } service_producer = "netapp.servicenetworking.goog" + deletion_policy = "ABANDON" } ] } From 6a3c7fe4445ca3f801db6a7d19cdd47587fe6a35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Niesiob=C4=99dzki?= Date: Sun, 12 May 2024 12:00:39 +0200 Subject: [PATCH 23/31] CloudSQL PSC Endpoints support (#2242) * Add PSC endpoints consumers to net-address * Cloud SQL E2E tests --- modules/cloudsql-instance/README.md | 175 ++++++++++-------- modules/cloudsql-instance/variables.tf | 2 +- modules/net-address/README.md | 25 ++- modules/net-address/main.tf | 42 +---- modules/net-address/psc.tf | 102 ++++++++++ modules/net-address/variables.tf | 26 ++- tests/collectors.py | 11 +- tests/examples/variables.tf | 2 +- .../setup_module/e2e_tests.tfvars.tftpl | 6 + tests/examples_e2e/setup_module/main.tf | 46 ++++- tests/fixtures.py | 11 +- tests/fixtures/cloudsql-instance.tf | 33 ++++ tests/fixtures/cloudsql-kms-iam-grant.tf | 23 +++ .../cloudsql_instance/examples/insights.yaml | 4 +- .../cloudsql_instance/examples/psc.yaml | 38 ++++ .../cloudsql_instance/examples/public-ip.yaml | 25 +-- .../cloudsql_instance/examples/replicas.yaml | 2 +- .../cloudsql_instance/examples/simple.yaml | 40 ++-- .../examples/psc-service-attachment.yaml | 29 +++ 19 files changed, 464 insertions(+), 178 deletions(-) create mode 100644 modules/net-address/psc.tf create mode 100644 tests/fixtures/cloudsql-instance.tf create mode 100644 tests/fixtures/cloudsql-kms-iam-grant.tf create mode 100644 tests/modules/cloudsql_instance/examples/psc.yaml create mode 100644 tests/modules/net_address/examples/psc-service-attachment.yaml diff --git a/modules/cloudsql-instance/README.md b/modules/cloudsql-instance/README.md index c1474cec1a..6ecf42a32d 100644 --- a/modules/cloudsql-instance/README.md +++ b/modules/cloudsql-instance/README.md @@ -1,4 +1,4 @@ -# Cloud SQL instance with read replicas +# Cloud SQL instance module This module manages the creation of Cloud SQL instances with potential read replicas in other regions. It can also create an initial set of users and databases via the `users` and `databases` parameters. @@ -6,7 +6,24 @@ Note that this module assumes that some options are the same for both the primar *Warning:* if you use the `users` field, you terraform state will contain each user's password in plain text. -## Simple example + +- [Examples](#examples) + - [Simple example](#simple-example) + - [Cross-regional read replica](#cross-regional-read-replica) + - [Custom flags, databases and users](#custom-flags-databases-and-users) + - [CMEK encryption](#cmek-encryption) + - [Instance with PSC enabled](#instance-with-psc-enabled) + - [Enable public IP](#enable-public-ip) + - [Query Insights](#query-insights) + - [Maintenance Config](#maintenance-config) + - [SSL Config](#ssl-config) +- [Variables](#variables) +- [Outputs](#outputs) +- [Fixtures](#fixtures) + + +## Examples +### Simple example This example shows how to setup a project, VPC and a standalone Cloud SQL instance. @@ -14,10 +31,12 @@ This example shows how to setup a project, VPC and a standalone Cloud SQL instan module "project" { source = "./fabric/modules/project" billing_account = var.billing_account_id - parent = var.organization_id - name = "my-db-project" + parent = var.folder_id + name = "db-prj" + prefix = var.prefix services = [ - "servicenetworking.googleapis.com" + "servicenetworking.googleapis.com", + "sqladmin.googleapis.com", ] } @@ -25,9 +44,18 @@ module "vpc" { source = "./fabric/modules/net-vpc" project_id = module.project.project_id name = "my-network" + # need only one - psa_config or subnets_psc psa_configs = [{ - ranges = { cloud-sql = "10.60.0.0/16" } + ranges = { cloud-sql = "10.60.0.0/16" } + deletion_policy = "ABANDON" }] + subnets_psc = [ + { + ip_cidr_range = "10.0.3.0/24" + name = "psc" + region = var.region + } + ] } module "db" { @@ -38,17 +66,20 @@ module "db" { psa_config = { private_network = module.vpc.self_link } + # psc_allowed_consumer_projects = [var.project_id] } } - name = "db" - region = "europe-west1" - database_version = "POSTGRES_13" - tier = "db-g1-small" + name = "db" + region = var.region + database_version = "POSTGRES_13" + tier = "db-g1-small" + gcp_deletion_protection = false + terraform_deletion_protection = false } -# tftest modules=3 resources=11 inventory=simple.yaml +# tftest modules=3 resources=14 inventory=simple.yaml e2e ``` -## Cross-regional read replica +### Cross-regional read replica ```hcl module "db" { @@ -61,21 +92,23 @@ module "db" { } } } - prefix = "myprefix" name = "db" - region = "europe-west1" + prefix = "myprefix" + region = var.region database_version = "POSTGRES_13" tier = "db-g1-small" replicas = { - replica1 = { region = "europe-west3", encryption_key_name = null } - replica2 = { region = "us-central1", encryption_key_name = null } + replica1 = { region = "europe-west3" } + replica2 = { region = "us-central1" } } + gcp_deletion_protection = false + terraform_deletion_protection = false } -# tftest modules=1 resources=3 inventory=replicas.yaml +# tftest modules=1 resources=3 inventory=replicas.yaml e2e ``` -## Custom flags, databases and users +### Custom flags, databases and users ```hcl module "db" { @@ -89,7 +122,7 @@ module "db" { } } name = "db" - region = "europe-west1" + region = var.region database_version = "MYSQL_8_0" tier = "db-g1-small" @@ -112,47 +145,19 @@ module "db" { password = "mypassword" } } + gcp_deletion_protection = false + terraform_deletion_protection = false } -# tftest modules=1 resources=6 inventory=custom.yaml +# tftest modules=1 resources=6 inventory=custom.yaml e2e ``` ### CMEK encryption ```hcl - -module "project" { - source = "./fabric/modules/project" - billing_account = var.billing_account_id - parent = var.organization_id - name = "my-db-project" - services = [ - "servicenetworking.googleapis.com", - "sqladmin.googleapis.com", - ] -} - -module "kms" { - source = "./fabric/modules/kms" - project_id = module.project.project_id - keyring = { - name = "keyring" - location = var.region - } - keys = { - key-sql = { - iam = { - "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [ - "serviceAccount:${module.project.service_accounts.robots.sqladmin}" - ] - } - } - } -} - module "db" { source = "./fabric/modules/cloudsql-instance" - project_id = module.project.project_id - encryption_key_name = module.kms.keys["key-sql"].id + project_id = var.project_id + encryption_key_name = var.kms_key.id network_config = { connectivity = { psa_config = { @@ -160,13 +165,15 @@ module "db" { } } } - name = "db" - region = var.region - database_version = "POSTGRES_13" - tier = "db-g1-small" + name = "db" + region = var.region + database_version = "POSTGRES_13" + tier = "db-g1-small" + gcp_deletion_protection = false + terraform_deletion_protection = false } -# tftest modules=3 resources=10 +# tftest modules=1 resources=2 fixtures=fixtures/cloudsql-kms-iam-grant.tf e2e ``` ### Instance with PSC enabled @@ -177,22 +184,25 @@ module "db" { project_id = var.project_id network_config = { connectivity = { - psc_allowed_consumer_projects = ["my-project-id"] + psc_allowed_consumer_projects = [var.project_id] } } prefix = "myprefix" name = "db" - region = "europe-west1" + region = var.region availability_type = "REGIONAL" database_version = "POSTGRES_13" tier = "db-g1-small" + + gcp_deletion_protection = false + terraform_deletion_protection = false } -# tftest modules=1 resources=1 +# tftest modules=1 resources=1 inventory=psc.yaml e2e ``` ### Enable public IP -Use `ipv_enabled` to create instances with a public IP. +Use `public_ipv4` to create instances with a public IP. ```hcl module "db" { @@ -206,15 +216,14 @@ module "db" { } } } - name = "db" - region = "europe-west1" - tier = "db-g1-small" - database_version = "MYSQL_8_0" - replicas = { - replica1 = { region = "europe-west3", encryption_key_name = null } - } + name = "db" + region = var.region + tier = "db-g1-small" + database_version = "MYSQL_8_0" + gcp_deletion_protection = false + terraform_deletion_protection = false } -# tftest modules=1 resources=2 inventory=public-ip.yaml +# tftest modules=1 resources=1 inventory=public-ip.yaml e2e ``` ### Query Insights @@ -233,15 +242,17 @@ module "db" { } } name = "db" - region = "europe-west1" + region = var.region database_version = "POSTGRES_13" tier = "db-g1-small" insights_config = { query_string_length = 2048 } + gcp_deletion_protection = false + terraform_deletion_protection = false } -# tftest modules=1 resources=1 inventory=insights.yaml +# tftest modules=1 resources=1 inventory=insights.yaml e2e ``` ### Maintenance Config @@ -260,13 +271,15 @@ module "db" { } } name = "db" - region = "europe-west1" + region = var.region database_version = "POSTGRES_13" tier = "db-g1-small" - maintenance_config = {} + maintenance_config = {} + gcp_deletion_protection = false + terraform_deletion_protection = false } -# tftest modules=1 resources=1 +# tftest modules=1 resources=1 e2e ``` ### SSL Config @@ -285,13 +298,15 @@ module "db" { } } name = "db" - region = "europe-west1" + region = var.region database_version = "POSTGRES_13" tier = "db-g1-small" - ssl = {} + ssl = {} + gcp_deletion_protection = false + terraform_deletion_protection = false } -# tftest modules=1 resources=1 +# tftest modules=1 resources=1 e2e ``` ## Variables @@ -322,7 +337,7 @@ module "db" { | [labels](variables.tf#L140) | Labels to be attached to all instances. | map(string) | | null | | [maintenance_config](variables.tf#L146) | Set maintenance window configuration and maintenance deny period (up to 90 days). Date format: 'yyyy-mm-dd'. | object({…}) | | {} | | [prefix](variables.tf#L207) | Optional prefix used to generate instance names. | string | | null | -| [replicas](variables.tf#L227) | Map of NAME=> {REGION, KMS_KEY} for additional read replicas. Set to null to disable replica creation. | map(object({…})) | | {} | +| [replicas](variables.tf#L227) | Map of NAME=> {REGION, KMS_KEY} for additional read replicas. Set to null to disable replica creation. | map(object({…})) | | {} | | [root_password](variables.tf#L236) | Root password of the Cloud SQL instance. Required for MS SQL Server. | string | | null | | [ssl](variables.tf#L242) | Setting to enable SSL, set config and certificates. | object({…}) | | {} | | [terraform_deletion_protection](variables.tf#L258) | Prevent terraform from deleting instances. | bool | | true | @@ -350,4 +365,8 @@ module "db" { | [self_link](outputs.tf#L114) | Self link of the primary instance. | | | [self_links](outputs.tf#L119) | Self links of all instances. | | | [user_passwords](outputs.tf#L127) | Map of containing the password of all users created through terraform. | ✓ | + +## Fixtures + +- [cloudsql-kms-iam-grant.tf](../../tests/fixtures/cloudsql-kms-iam-grant.tf) diff --git a/modules/cloudsql-instance/variables.tf b/modules/cloudsql-instance/variables.tf index e67dd3979a..613e106e74 100644 --- a/modules/cloudsql-instance/variables.tf +++ b/modules/cloudsql-instance/variables.tf @@ -228,7 +228,7 @@ variable "replicas" { description = "Map of NAME=> {REGION, KMS_KEY} for additional read replicas. Set to null to disable replica creation." type = map(object({ region = string - encryption_key_name = string + encryption_key_name = optional(string) })) default = {} } diff --git a/modules/net-address/README.md b/modules/net-address/README.md index 55b84108a2..5f53063368 100644 --- a/modules/net-address/README.md +++ b/modules/net-address/README.md @@ -122,6 +122,26 @@ module "addresses" { # tftest modules=1 resources=1 inventory=psc.yaml e2e ``` +To create PSC address targeting a service regional provider use the `service_attachment` property. +```hcl +module "addresses" { + source = "./fabric/modules/net-address" + project_id = var.project_id + psc_addresses = { + cloudsql-one = { + address = "10.0.16.32" + subnet_self_link = var.subnet.self_link + region = var.region + service_attachment = { + psc_service_attachment_link = module.cloudsql-instance.psc_service_attachment_link + } + } + } +} +# tftest modules=2 resources=3 fixtures=fixtures/cloudsql-instance.tf inventory=psc-service-attachment.yaml e2e +``` + + ### IPSec Interconnect addresses ```hcl @@ -176,8 +196,8 @@ module "addresses" { | [internal_addresses](variables.tf#L50) | Map of internal addresses to create, keyed by name. | map(object({…})) | | {} | | [ipsec_interconnect_addresses](variables.tf#L65) | Map of internal addresses used for HPA VPN over Cloud Interconnect. | map(object({…})) | | {} | | [network_attachments](variables.tf#L84) | PSC network attachments, names as keys. | map(object({…})) | | {} | -| [psa_addresses](variables.tf#L102) | Map of internal addresses used for Private Service Access. | map(object({…})) | | {} | -| [psc_addresses](variables.tf#L115) | Map of internal addresses used for Private Service Connect. | map(object({…})) | | {} | +| [psa_addresses](variables.tf#L102) | Map of internal addresses used for Private Service Access. | map(object({…})) | | {} | +| [psc_addresses](variables.tf#L114) | Map of internal addresses used for Private Service Connect. | map(object({…})) | | {} | ## Outputs @@ -193,5 +213,6 @@ module "addresses" { ## Fixtures +- [cloudsql-instance.tf](../../tests/fixtures/cloudsql-instance.tf) - [net-vpc-ipv6.tf](../../tests/fixtures/net-vpc-ipv6.tf) diff --git a/modules/net-address/main.tf b/modules/net-address/main.tf index 20b8b6a16e..e7c69413cd 100644 --- a/modules/net-address/main.tf +++ b/modules/net-address/main.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * Copyright 2024 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,20 +14,6 @@ * limitations under the License. */ -locals { - network_attachments = { - for k, v in var.network_attachments : k => merge(v, { - region = regex("regions/([^/]+)", v.subnet_self_link)[0] - # not using the full self link generates a permadiff - subnet_self_link = ( - startswith(v.subnet_self_link, "https://") - ? v.subnet_self_link - : "https://www.googleapis.com/compute/v1/${v.subnet_self_link}" - ) - }) - } -} - resource "google_compute_global_address" "global" { for_each = var.global_addresses project = var.project_id @@ -66,18 +52,6 @@ resource "google_compute_address" "internal" { subnetwork = each.value.subnetwork } -resource "google_compute_global_address" "psc" { - for_each = var.psc_addresses - project = var.project_id - name = coalesce(each.value.name, each.key) - description = each.value.description - address = try(each.value.address, null) - address_type = "INTERNAL" - network = each.value.network - purpose = "PRIVATE_SERVICE_CONNECT" - # labels = lookup(var.internal_address_labels, each.key, {}) -} - resource "google_compute_global_address" "psa" { for_each = var.psa_addresses project = var.project_id @@ -104,17 +78,3 @@ resource "google_compute_address" "ipsec_interconnect" { purpose = "IPSEC_INTERCONNECT" } -resource "google_compute_network_attachment" "default" { - provider = google-beta - for_each = local.network_attachments - project = var.project_id - region = each.value.region - name = each.key - description = each.value.description - connection_preference = ( - each.value.automatic_connection ? "ACCEPT_AUTOMATIC" : "ACCEPT_MANUAL" - ) - subnetworks = [each.value.subnet_self_link] - producer_accept_lists = each.value.producer_accept_lists - producer_reject_lists = each.value.producer_reject_lists -} diff --git a/modules/net-address/psc.tf b/modules/net-address/psc.tf new file mode 100644 index 0000000000..2fbf75788f --- /dev/null +++ b/modules/net-address/psc.tf @@ -0,0 +1,102 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +locals { + network_attachments = { + for k, v in var.network_attachments : k => merge(v, { + region = regex("regions/([^/]+)", v.subnet_self_link)[0] + # not using the full self link generates a permadiff + subnet_self_link = ( + startswith(v.subnet_self_link, "https://") + ? v.subnet_self_link + : "https://www.googleapis.com/compute/v1/${v.subnet_self_link}" + ) + }) + } + regional_psc = { + for name, psc in var.psc_addresses : name => psc if psc.region != null + + } + global_psc = { + for name, psc in var.psc_addresses : name => psc if psc.region == null + } +} + +resource "google_compute_network_attachment" "default" { + provider = google-beta + for_each = local.network_attachments + project = var.project_id + region = each.value.region + name = each.key + description = each.value.description + connection_preference = ( + each.value.automatic_connection ? "ACCEPT_AUTOMATIC" : "ACCEPT_MANUAL" + ) + subnetworks = [each.value.subnet_self_link] + producer_accept_lists = each.value.producer_accept_lists + producer_reject_lists = each.value.producer_reject_lists +} + +# global PSC services +resource "google_compute_global_address" "psc" { + for_each = local.global_psc + project = var.project_id + name = coalesce(each.value.name, each.key) + description = each.value.description + address = try(each.value.address, null) + address_type = "INTERNAL" + network = each.value.network + purpose = "PRIVATE_SERVICE_CONNECT" + # labels = lookup(var.internal_address_labels, each.key, {}) +} + +resource "google_compute_global_forwarding_rule" "psc_consumer" { + for_each = { for name, psc in local.global_psc : name => psc if psc.service_attachment != null } + name = coalesce(each.value.name, each.key) + project = var.project_id + subnetwork = each.value.subnet_self_link + ip_address = google_compute_global_address.psc[each.key].self_link + load_balancing_scheme = "" + target = each.value.service_attachment.psc_service_attachment_link +} + +# regional PSC services +resource "google_compute_address" "psc" { + for_each = local.regional_psc + project = var.project_id + name = coalesce(each.value.name, each.key) + address = try(each.value.address, null) + address_type = "INTERNAL" + description = each.value.description + network = each.value.network + # purpose not applicable for regional address + # purpose = "PRIVATE_SERVICE_CONNECT" + region = each.value.region + subnetwork = each.value.subnet_self_link + # labels = lookup(var.internal_address_labels, each.key, {}) +} + +resource "google_compute_forwarding_rule" "psc_consumer" { + for_each = { for name, psc in local.regional_psc : name => psc if psc.service_attachment != null } + name = coalesce(each.value.name, each.key) + project = var.project_id + region = each.value.region + subnetwork = each.value.subnet_self_link + ip_address = google_compute_address.psc[each.key].self_link + load_balancing_scheme = "" + recreate_closed_psc = true + target = each.value.service_attachment.psc_service_attachment_link +} diff --git a/modules/net-address/variables.tf b/modules/net-address/variables.tf index 190bffdde6..236c23960a 100644 --- a/modules/net-address/variables.tf +++ b/modules/net-address/variables.tf @@ -107,7 +107,6 @@ variable "psa_addresses" { prefix_length = number description = optional(string, "Terraform managed.") name = optional(string) - })) default = {} } @@ -115,10 +114,27 @@ variable "psa_addresses" { variable "psc_addresses" { description = "Map of internal addresses used for Private Service Connect." type = map(object({ - address = string - network = string - description = optional(string, "Terraform managed.") - name = optional(string) + address = string + description = optional(string, "Terraform managed.") + name = optional(string) + network = optional(string) + region = optional(string) + subnet_self_link = optional(string) + service_attachment = optional(object({ # so we can safely check if service_attachemnt != null in for_each + psc_service_attachment_link = string + })) })) default = {} + validation { + condition = alltrue([for key, value in var.psc_addresses : (value.region != null || (value.region == null && value.network != null))]) + error_message = "Provide network if creating global PSC addresses / endpoints." + } + validation { + condition = alltrue([for key, value in var.psc_addresses : (value.region == null || (value.region != null && value.subnet_self_link != null))]) + error_message = "Provide subnet_self_link if creating regional PSC addresses / endpoints." + } + validation { + condition = alltrue([for key, value in var.psc_addresses : !(value.subnet_self_link != null && value.network != null)]) + error_message = "Do not provide network and subnet_self_link at the same time" + } } diff --git a/tests/collectors.py b/tests/collectors.py index 749e192c50..310b8151cc 100644 --- a/tests/collectors.py +++ b/tests/collectors.py @@ -87,8 +87,15 @@ def __init__(self, name, parent, module, inventory, tf_var_files, self.extra_files = extra_files def runtest(self): - s = plan_validator(self.module, self.inventory, self.parent.path.parent, - self.tf_var_files, self.extra_files) + try: + summary = plan_validator(self.module, self.inventory, self.parent.path.parent, + self.tf_var_files, self.extra_files) + except AssertionError: + def full_paths(x): + return [(self.parent.path.parent / x ) for x in x] + print(f'Error in inventory file: {" ".join(full_paths(self.inventory))}') + print(f'To regenerate inventory run: python tools/plan_summary.py {self.module} {" ".join(full_paths(self.tf_var_files))}') + raise def reportinfo(self): return self.path, None, self.name diff --git a/tests/examples/variables.tf b/tests/examples/variables.tf index 29dc8d8940..d999609b0e 100644 --- a/tests/examples/variables.tf +++ b/tests/examples/variables.tf @@ -84,7 +84,7 @@ variable "subnet_psc_1" { name = "subnet_name" region = "subnet_region" cidr = "subnet_cidr" - self_link = "subnet_self_link" + self_link = "https://www.googleapis.com/compute/v1/projects/my-project/regions/europe-west8/subnetworks/subnet" } } diff --git a/tests/examples_e2e/setup_module/e2e_tests.tfvars.tftpl b/tests/examples_e2e/setup_module/e2e_tests.tfvars.tftpl index 5bb6d779ec..fd63d450fe 100644 --- a/tests/examples_e2e/setup_module/e2e_tests.tfvars.tftpl +++ b/tests/examples_e2e/setup_module/e2e_tests.tfvars.tftpl @@ -37,6 +37,12 @@ subnet = { cidr = "${subnet.ip_cidr_range}" self_link = "${subnet.self_link}" } +subnet_psc_1 = { + name = "${subnet_psc_1.name}" + region = "${subnet_psc_1.region}" + cidr = "${subnet_psc_1.ip_cidr_range}" + self_link = "${subnet_psc_1.self_link}" +} vpc = { name = "${vpc.name}" self_link = "${vpc.self_link}" diff --git a/tests/examples_e2e/setup_module/main.tf b/tests/examples_e2e/setup_module/main.tf index bd48e4fbd9..4e1d1931bf 100644 --- a/tests/examples_e2e/setup_module/main.tf +++ b/tests/examples_e2e/setup_module/main.tf @@ -15,7 +15,8 @@ locals { prefix = "${var.prefix}-${var.timestamp}${var.suffix}" jit_services = [ - "storage.googleapis.com", # no permissions granted by default + "storage.googleapis.com", # no permissions granted by default + "sqladmin.googleapis.com", # roles/cloudsql.serviceAgent ] services = [ # trimmed down list of services, to be extended as needed @@ -35,6 +36,7 @@ locals { "secretmanager.googleapis.com", "servicenetworking.googleapis.com", "serviceusage.googleapis.com", + "sqladmin.googleapis.com", "stackdriver.googleapis.com", "storage-component.googleapis.com", "storage.googleapis.com", @@ -114,6 +116,36 @@ resource "google_compute_subnetwork" "proxy_only_regional" { role = "ACTIVE" } +resource "google_compute_subnetwork" "psc" { + project = google_project.project.project_id + network = google_compute_network.network.name + name = "psc-regional" + region = var.region + ip_cidr_range = "10.0.19.0/24" + purpose = "PRIVATE_SERVICE_CONNECT" +} + +### PSA ### + +resource "google_compute_global_address" "psa_ranges" { + project = google_project.project.project_id + network = google_compute_network.network.id + name = "psa-range" + purpose = "VPC_PEERING" + address_type = "INTERNAL" + address = "10.0.20.0" + prefix_length = 22 +} + +resource "google_service_networking_connection" "psa_connection" { + network = google_compute_network.network.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.psa_ranges.name] + deletion_policy = "ABANDON" +} + +### END OF PSA + resource "google_service_account" "service_account" { account_id = "e2e-service-account" project = google_project.project.project_id @@ -141,6 +173,12 @@ resource "google_project_service_identity" "jit_si" { depends_on = [google_project_service.project_service] } +resource "google_project_iam_binding" "cloudsql_agent" { + members = ["serviceAccount:service-${google_project.project.number}@gcp-sa-cloud-sql.iam.gserviceaccount.com"] + project = google_project.project.project_id + role = "roles/cloudsql.serviceAgent" + depends_on = [google_project_service_identity.jit_si] +} resource "local_file" "terraform_tfvars" { filename = "e2e_tests.tfvars" @@ -168,6 +206,12 @@ resource "local_file" "terraform_tfvars" { ip_cidr_range = google_compute_subnetwork.subnetwork.ip_cidr_range self_link = google_compute_subnetwork.subnetwork.self_link } + subnet_psc_1 = { + name = google_compute_subnetwork.psc.name + region = google_compute_subnetwork.psc.region + ip_cidr_range = google_compute_subnetwork.psc.ip_cidr_range + self_link = google_compute_subnetwork.psc.self_link + } vpc = { name = google_compute_network.network.name self_link = google_compute_network.network.self_link diff --git a/tests/fixtures.py b/tests/fixtures.py index 50b6d8ad6a..1d725135f4 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -186,8 +186,13 @@ def plan_validator(module_path, inventory_paths, basedir, tf_var_files=None, # print(yaml.dump({'counts': summary.counts})) if 'values' in inventory: - validate_plan_object(inventory['values'], summary.values, relative_path, - "") + try: + validate_plan_object(inventory['values'], summary.values, relative_path, + "") + except AssertionError: + print(f'\n{path}') + print(yaml.dump({'values': summary.values})) + raise if 'counts' in inventory: try: @@ -199,6 +204,7 @@ def plan_validator(module_path, inventory_paths, basedir, tf_var_files=None, assert plan_count == expected_count, \ f'{relative_path}: count of {type_} resources failed. Got {plan_count}, expected {expected_count}' except AssertionError: + print(f'\n{path}') print(yaml.dump({'counts': summary.counts})) raise @@ -218,6 +224,7 @@ def plan_validator(module_path, inventory_paths, basedir, tf_var_files=None, f'{relative_path}: output {output_name} failed. Got `{plan_output}`, expected `{expected_output}`' except AssertionError: if _buffer: + print(f'\n{path}') print(yaml.dump(_buffer)) raise return summary diff --git a/tests/fixtures/cloudsql-instance.tf b/tests/fixtures/cloudsql-instance.tf new file mode 100644 index 0000000000..6fed8d6ada --- /dev/null +++ b/tests/fixtures/cloudsql-instance.tf @@ -0,0 +1,33 @@ +/** + * Copyright 2024 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. + */ + +module "cloudsql-instance" { + source = "./fabric/modules/cloudsql-instance" + project_id = var.project_id + network_config = { + connectivity = { + psc_allowed_consumer_projects = [var.project_id] + } + } + ## define a consumer project with an endpoint within the project + name = "db" + region = var.region + availability_type = "REGIONAL" + database_version = "POSTGRES_13" + tier = "db-g1-small" + gcp_deletion_protection = false + terraform_deletion_protection = false +} diff --git a/tests/fixtures/cloudsql-kms-iam-grant.tf b/tests/fixtures/cloudsql-kms-iam-grant.tf new file mode 100644 index 0000000000..9ea53a0268 --- /dev/null +++ b/tests/fixtures/cloudsql-kms-iam-grant.tf @@ -0,0 +1,23 @@ +/** + * Copyright 2024 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_kms_crypto_key_iam_binding" "encrypt_decrypt" { + crypto_key_id = var.kms_key.id + members = [ + "serviceAccount:service-${var.project_number}@gcp-sa-cloud-sql.iam.gserviceaccount.com" + ] + role = "roles/cloudkms.cryptoKeyEncrypterDecrypter" +} diff --git a/tests/modules/cloudsql_instance/examples/insights.yaml b/tests/modules/cloudsql_instance/examples/insights.yaml index 84a4245ddf..a2c46f5b9b 100644 --- a/tests/modules/cloudsql_instance/examples/insights.yaml +++ b/tests/modules/cloudsql_instance/examples/insights.yaml @@ -17,11 +17,11 @@ values: database_version: POSTGRES_13 name: db project: project-id - region: europe-west1 + region: europe-west8 settings: - activation_policy: ALWAYS availability_type: ZONAL - deletion_protection_enabled: true + deletion_protection_enabled: false disk_autoresize: true disk_type: PD_SSD insights_config: diff --git a/tests/modules/cloudsql_instance/examples/psc.yaml b/tests/modules/cloudsql_instance/examples/psc.yaml new file mode 100644 index 0000000000..722700d7bd --- /dev/null +++ b/tests/modules/cloudsql_instance/examples/psc.yaml @@ -0,0 +1,38 @@ +# Copyright 2024 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.db.google_sql_database_instance.primary: + database_version: POSTGRES_13 + deletion_protection: false + name: myprefix-db + project: project-id + region: europe-west8 + settings: + - activation_policy: ALWAYS + availability_type: REGIONAL + deletion_protection_enabled: false + ip_configuration: + - ipv4_enabled: false + private_network: null + psc_config: + - allowed_consumer_projects: + - project-id + psc_enabled: true + tier: db-g1-small + +counts: + google_sql_database_instance: 1 + modules: 1 + resources: 1 diff --git a/tests/modules/cloudsql_instance/examples/public-ip.yaml b/tests/modules/cloudsql_instance/examples/public-ip.yaml index f3ca86d3a5..e216ee3b0f 100644 --- a/tests/modules/cloudsql_instance/examples/public-ip.yaml +++ b/tests/modules/cloudsql_instance/examples/public-ip.yaml @@ -17,30 +17,11 @@ values: database_version: MYSQL_8_0 name: db project: project-id - region: europe-west1 + region: europe-west8 settings: - activation_policy: ALWAYS availability_type: ZONAL - deletion_protection_enabled: true - disk_autoresize: true - disk_type: PD_SSD - insights_config: [] - ip_configuration: - - allocated_ip_range: null - authorized_networks: [] - ipv4_enabled: true - private_network: projects/xxx/global/networks/aaa - tier: db-g1-small - module.db.google_sql_database_instance.replicas["replica1"]: - database_version: MYSQL_8_0 - master_instance_name: db - name: replica1 - project: project-id - region: europe-west3 - settings: - - activation_policy: ALWAYS - availability_type: ZONAL - deletion_protection_enabled: true + deletion_protection_enabled: false disk_autoresize: true disk_type: PD_SSD insights_config: [] @@ -52,4 +33,4 @@ values: tier: db-g1-small counts: - google_sql_database_instance: 2 + google_sql_database_instance: 1 diff --git a/tests/modules/cloudsql_instance/examples/replicas.yaml b/tests/modules/cloudsql_instance/examples/replicas.yaml index 1ed30f9bc9..bb337d4457 100644 --- a/tests/modules/cloudsql_instance/examples/replicas.yaml +++ b/tests/modules/cloudsql_instance/examples/replicas.yaml @@ -18,7 +18,7 @@ values: database_version: POSTGRES_13 name: myprefix-db project: project-id - region: europe-west1 + region: europe-west8 module.db.google_sql_database_instance.replicas["replica1"]: clone: [] database_version: POSTGRES_13 diff --git a/tests/modules/cloudsql_instance/examples/simple.yaml b/tests/modules/cloudsql_instance/examples/simple.yaml index 5103c12b4e..4221eb288c 100644 --- a/tests/modules/cloudsql_instance/examples/simple.yaml +++ b/tests/modules/cloudsql_instance/examples/simple.yaml @@ -16,10 +16,10 @@ values: module.db.google_sql_database_instance.primary: clone: [] database_version: POSTGRES_13 - deletion_protection: true + deletion_protection: false name: db - project: my-db-project - region: europe-west1 + project: test-db-prj + region: europe-west8 restore_backup_context: [] root_password: null settings: @@ -30,7 +30,7 @@ values: collation: null data_cache_config: [] database_flags: [] - deletion_protection_enabled: true + deletion_protection_enabled: false deny_maintenance_period: [] disk_autoresize: true disk_autoresize_limit: 0 @@ -54,25 +54,25 @@ values: module.project.google_project.project[0]: auto_create_network: false billing_account: 123456-123456-123456 - folder_id: null + folder_id: '1122334455' labels: null - name: my-db-project - org_id: '1122334455' - project_id: my-db-project + name: test-db-prj + org_id: null + project_id: test-db-prj skip_delete: false timeouts: null module.project.google_project_iam_member.servicenetworking[0]: condition: [] - project: my-db-project + project: test-db-prj role: roles/servicenetworking.serviceAgent module.project.google_project_service.project_services["servicenetworking.googleapis.com"]: disable_dependent_services: false disable_on_destroy: false - project: my-db-project + project: test-db-prj service: servicenetworking.googleapis.com timeouts: null module.project.google_project_service_identity.servicenetworking[0]: - project: my-db-project + project: test-db-prj service: servicenetworking.googleapis.com timeouts: null module.vpc.google_compute_global_address.psa_ranges["servicenetworking-googleapis-com-cloud-sql"]: @@ -82,7 +82,7 @@ values: ip_version: null name: servicenetworking-googleapis-com-cloud-sql prefix_length: 16 - project: my-db-project + project: test-db-prj purpose: VPC_PEERING timeouts: null module.vpc.google_compute_network.network[0]: @@ -92,14 +92,14 @@ values: enable_ula_internal_ipv6: null name: my-network network_firewall_policy_enforcement_order: AFTER_CLASSIC_FIREWALL - project: my-db-project + project: test-db-prj routing_mode: GLOBAL timeouts: null module.vpc.google_compute_network_peering_routes_config.psa_routes["servicenetworking.googleapis.com"]: export_custom_routes: false import_custom_routes: false network: my-network - project: my-db-project + project: test-db-prj timeouts: null module.vpc.google_compute_route.gateway["private-googleapis"]: description: Terraform-managed. @@ -111,7 +111,7 @@ values: next_hop_instance: null next_hop_vpn_tunnel: null priority: 1000 - project: my-db-project + project: test-db-prj tags: null timeouts: null module.vpc.google_compute_route.gateway["restricted-googleapis"]: @@ -124,11 +124,11 @@ values: next_hop_instance: null next_hop_vpn_tunnel: null priority: 1000 - project: my-db-project + project: test-db-prj tags: null timeouts: null module.vpc.google_service_networking_connection.psa_connection["servicenetworking.googleapis.com"]: - deletion_policy: null + deletion_policy: ABANDON reserved_peering_ranges: - servicenetworking-googleapis-com-cloud-sql service: servicenetworking.googleapis.com @@ -141,11 +141,11 @@ counts: google_compute_route: 2 google_project: 1 google_project_iam_member: 1 - google_project_service: 1 - google_project_service_identity: 1 + google_project_service: 2 + google_project_service_identity: 2 google_service_networking_connection: 1 google_sql_database_instance: 1 modules: 3 - resources: 11 + resources: 14 outputs: {} diff --git a/tests/modules/net_address/examples/psc-service-attachment.yaml b/tests/modules/net_address/examples/psc-service-attachment.yaml new file mode 100644 index 0000000000..7c3eaca02a --- /dev/null +++ b/tests/modules/net_address/examples/psc-service-attachment.yaml @@ -0,0 +1,29 @@ +# Copyright 2024 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.addresses.google_compute_forwarding_rule.psc_consumer["cloudsql-one"]: + load_balancing_scheme: '' + name: cloudsql-one + project: project-id + recreate_closed_psc: true + region: europe-west8 + subnetwork: subnet_self_link + module.addresses.google_compute_address.psc["cloudsql-one"]: + address: 10.0.16.32 + address_type: INTERNAL + description: Terraform managed. + name: cloudsql-one + project: project-id + subnetwork: subnet_self_link From af253c97022068c8c0500988b45e1ef0f727c7fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Niesiob=C4=99dzki?= Date: Sun, 12 May 2024 21:02:04 +0200 Subject: [PATCH 24/31] Fix 0-bootstrap iam_by_principals not taking into account all principals (#2267) * Fix 0-bootstrap iam_by_principals not taking into account all principals * Add test-case for iam_by_principals for 0-bootstrap stage --------- Co-authored-by: Ludovico Magnocavallo --- fast/stages/0-bootstrap/organization.tf | 10 +++++++-- tests/collectors.py | 2 +- .../s0_bootstrap/iam_by_principals.tfvars | 20 +++++++++++++++++ .../s0_bootstrap/iam_by_principals.yaml | 22 +++++++++++++++++++ tests/fast/stages/s0_bootstrap/tftest.yaml | 2 ++ 5 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 tests/fast/stages/s0_bootstrap/iam_by_principals.tfvars create mode 100644 tests/fast/stages/s0_bootstrap/iam_by_principals.yaml diff --git a/fast/stages/0-bootstrap/organization.tf b/fast/stages/0-bootstrap/organization.tf index f91b4e8c14..fdd08937a7 100644 --- a/fast/stages/0-bootstrap/organization.tf +++ b/fast/stages/0-bootstrap/organization.tf @@ -138,8 +138,14 @@ module "organization" { organization_id = module.organization-logging.id # human (groups) IAM bindings iam_by_principals = { - for k, v in local.iam_principals : - k => distinct(concat(v, lookup(var.iam_by_principals, k, []))) + for key in distinct(concat( + keys(local.iam_principals), + keys(var.iam_by_principals), + )) : + key => distinct(concat( + lookup(local.iam_principals, key, []), + lookup(var.iam_by_principals, key, []), + )) } # machine (service accounts) IAM bindings iam = merge( diff --git a/tests/collectors.py b/tests/collectors.py index 310b8151cc..9e26c06774 100644 --- a/tests/collectors.py +++ b/tests/collectors.py @@ -92,7 +92,7 @@ def runtest(self): self.tf_var_files, self.extra_files) except AssertionError: def full_paths(x): - return [(self.parent.path.parent / x ) for x in x] + return [str(self.parent.path.parent / x ) for x in x] print(f'Error in inventory file: {" ".join(full_paths(self.inventory))}') print(f'To regenerate inventory run: python tools/plan_summary.py {self.module} {" ".join(full_paths(self.tf_var_files))}') raise diff --git a/tests/fast/stages/s0_bootstrap/iam_by_principals.tfvars b/tests/fast/stages/s0_bootstrap/iam_by_principals.tfvars new file mode 100644 index 0000000000..4ceaffb6ac --- /dev/null +++ b/tests/fast/stages/s0_bootstrap/iam_by_principals.tfvars @@ -0,0 +1,20 @@ +organization = { + domain = "fast.example.com" + id = 123456789012 + customer_id = "C00000000" +} +billing_account = { + id = "000000-111111-222222" +} +essential_contacts = "gcp-organization-admins@fast.example.com" +iam_by_principals = { + "user:other@fast.example.com" = ["roles/browser"] +} +prefix = "fast" +org_policies_config = { + import_defaults = false +} +outputs_location = "/fast-config" +groups = { + gcp-support = "group:gcp-support@example.com" +} diff --git a/tests/fast/stages/s0_bootstrap/iam_by_principals.yaml b/tests/fast/stages/s0_bootstrap/iam_by_principals.yaml new file mode 100644 index 0000000000..83b965d302 --- /dev/null +++ b/tests/fast/stages/s0_bootstrap/iam_by_principals.yaml @@ -0,0 +1,22 @@ +# Copyright 2024 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.organization.google_organization_iam_binding.authoritative["roles/browser"]: + condition: [] + members: + - domain:fast.example.com + - user:other@fast.example.com + org_id: '123456789012' + role: roles/browser diff --git a/tests/fast/stages/s0_bootstrap/tftest.yaml b/tests/fast/stages/s0_bootstrap/tftest.yaml index 8f415a219a..2643714eb9 100644 --- a/tests/fast/stages/s0_bootstrap/tftest.yaml +++ b/tests/fast/stages/s0_bootstrap/tftest.yaml @@ -25,3 +25,5 @@ tests: - simple.yaml - simple_projects.yaml - simple_sas.yaml + + iam_by_principals: From 604920dec9ceb39e20b9768f482807f2675092ff Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Mon, 13 May 2024 09:24:17 +0200 Subject: [PATCH 25/31] add logging settings to folder module (#2268) --- modules/folder/README.md | 18 +++--- modules/folder/logging.tf | 7 ++ modules/folder/variables-logging.tf | 78 +++++++++++++++++++++++ modules/folder/variables.tf | 53 --------------- modules/organization/README.md | 15 +++-- modules/organization/variables-logging.tf | 78 +++++++++++++++++++++++ modules/organization/variables.tf | 63 ------------------ 7 files changed, 181 insertions(+), 131 deletions(-) create mode 100644 modules/folder/variables-logging.tf create mode 100644 modules/organization/variables-logging.tf diff --git a/modules/folder/README.md b/modules/folder/README.md index 2efb626b97..ff242aeec0 100644 --- a/modules/folder/README.md +++ b/modules/folder/README.md @@ -345,12 +345,13 @@ module "folder" { | name | description | resources | |---|---|---| | [iam.tf](./iam.tf) | IAM bindings. | google_folder_iam_binding · google_folder_iam_member | -| [logging.tf](./logging.tf) | Log sinks and supporting resources. | google_bigquery_dataset_iam_member · google_folder_iam_audit_config · google_logging_folder_exclusion · google_logging_folder_sink · google_project_iam_member · google_pubsub_topic_iam_member · google_storage_bucket_iam_member | +| [logging.tf](./logging.tf) | Log sinks and supporting resources. | google_bigquery_dataset_iam_member · google_folder_iam_audit_config · google_logging_folder_exclusion · google_logging_folder_settings · google_logging_folder_sink · google_project_iam_member · google_pubsub_topic_iam_member · google_storage_bucket_iam_member | | [main.tf](./main.tf) | Module-level locals and resources. | google_compute_firewall_policy_association · google_essential_contacts_contact · google_folder | | [organization-policies.tf](./organization-policies.tf) | Folder-level organization policies. | google_org_policy_policy | | [outputs.tf](./outputs.tf) | Module outputs. | | | [tags.tf](./tags.tf) | None | google_tags_tag_binding | | [variables-iam.tf](./variables-iam.tf) | None | | +| [variables-logging.tf](./variables-logging.tf) | None | | | [variables.tf](./variables.tf) | Module variables. | | | [versions.tf](./versions.tf) | Version pins. | | @@ -367,13 +368,14 @@ module "folder" { | [iam_bindings_additive](variables-iam.tf#L39) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | | [iam_by_principals](variables-iam.tf#L54) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable. | map(list(string)) | | {} | | [id](variables.tf#L48) | Folder ID in case you use folder_create=false. | string | | null | -| [logging_data_access](variables.tf#L54) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | map(map(list(string))) | | {} | -| [logging_exclusions](variables.tf#L69) | Logging exclusions for this folder in the form {NAME -> FILTER}. | map(string) | | {} | -| [logging_sinks](variables.tf#L76) | Logging sinks to create for the folder. | map(object({…})) | | {} | -| [name](variables.tf#L107) | Folder name. | string | | null | -| [org_policies](variables.tf#L113) | Organization policies applied to this folder keyed by policy name. | map(object({…})) | | {} | -| [parent](variables.tf#L140) | Parent in folders/folder_id or organizations/org_id format. | string | | null | -| [tag_bindings](variables.tf#L150) | Tag bindings for this folder, in key => tag value id format. | map(string) | | null | +| [logging_data_access](variables-logging.tf#L17) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | map(map(list(string))) | | {} | +| [logging_exclusions](variables-logging.tf#L32) | Logging exclusions for this folder in the form {NAME -> FILTER}. | map(string) | | {} | +| [logging_settings](variables-logging.tf#L39) | Default settings for logging resources. | object({…}) | | null | +| [logging_sinks](variables-logging.tf#L49) | Logging sinks to create for the folder. | map(object({…})) | | {} | +| [name](variables.tf#L54) | Folder name. | string | | null | +| [org_policies](variables.tf#L60) | Organization policies applied to this folder keyed by policy name. | map(object({…})) | | {} | +| [parent](variables.tf#L87) | Parent in folders/folder_id or organizations/org_id format. | string | | null | +| [tag_bindings](variables.tf#L97) | Tag bindings for this folder, in key => tag value id format. | map(string) | | null | ## Outputs diff --git a/modules/folder/logging.tf b/modules/folder/logging.tf index 817c4d1e06..bb42983c22 100644 --- a/modules/folder/logging.tf +++ b/modules/folder/logging.tf @@ -35,6 +35,13 @@ locals { } } +resource "google_logging_folder_settings" "default" { + count = var.logging_settings != null ? 1 : 0 + folder = local.folder_id + disable_default_sink = var.logging_settings.disable_default_sink + storage_location = var.logging_settings.storage_location +} + resource "google_folder_iam_audit_config" "default" { for_each = var.logging_data_access folder = local.folder_id diff --git a/modules/folder/variables-logging.tf b/modules/folder/variables-logging.tf new file mode 100644 index 0000000000..89685a6de9 --- /dev/null +++ b/modules/folder/variables-logging.tf @@ -0,0 +1,78 @@ +/** + * Copyright 2024 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. + */ + +variable "logging_data_access" { + description = "Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services." + type = map(map(list(string))) + nullable = false + default = {} + validation { + condition = alltrue(flatten([ + for k, v in var.logging_data_access : [ + for kk, vv in v : contains(["DATA_READ", "DATA_WRITE", "ADMIN_READ"], kk) + ] + ])) + error_message = "Log type keys for each service can only be one of 'DATA_READ', 'DATA_WRITE', 'ADMIN_READ'." + } +} + +variable "logging_exclusions" { + description = "Logging exclusions for this folder in the form {NAME -> FILTER}." + type = map(string) + default = {} + nullable = false +} + +variable "logging_settings" { + description = "Default settings for logging resources." + type = object({ + # TODO: add support for CMEK + disable_default_sink = optional(bool) + storage_location = optional(string) + }) + default = null +} + +variable "logging_sinks" { + description = "Logging sinks to create for the folder." + type = 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) + type = string + })) + default = {} + nullable = false + validation { + condition = alltrue([ + for k, v in var.logging_sinks : + contains(["bigquery", "logging", "project", "pubsub", "storage"], v.type) + ]) + error_message = "Type must be one of 'bigquery', 'logging', 'project', 'pubsub', 'storage'." + } + validation { + condition = alltrue([ + for k, v in var.logging_sinks : + v.bq_partitioned_table != true || v.type == "bigquery" + ]) + error_message = "Can only set bq_partitioned_table when type is `bigquery`." + } +} diff --git a/modules/folder/variables.tf b/modules/folder/variables.tf index 6da2685a71..e5f6d548be 100644 --- a/modules/folder/variables.tf +++ b/modules/folder/variables.tf @@ -51,59 +51,6 @@ variable "id" { default = null } -variable "logging_data_access" { - description = "Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services." - type = map(map(list(string))) - nullable = false - default = {} - validation { - condition = alltrue(flatten([ - for k, v in var.logging_data_access : [ - for kk, vv in v : contains(["DATA_READ", "DATA_WRITE", "ADMIN_READ"], kk) - ] - ])) - error_message = "Log type keys for each service can only be one of 'DATA_READ', 'DATA_WRITE', 'ADMIN_READ'." - } -} - -variable "logging_exclusions" { - description = "Logging exclusions for this folder in the form {NAME -> FILTER}." - type = map(string) - default = {} - nullable = false -} - -variable "logging_sinks" { - description = "Logging sinks to create for the folder." - type = 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) - type = string - })) - default = {} - nullable = false - validation { - condition = alltrue([ - for k, v in var.logging_sinks : - contains(["bigquery", "logging", "project", "pubsub", "storage"], v.type) - ]) - error_message = "Type must be one of 'bigquery', 'logging', 'project', 'pubsub', 'storage'." - } - validation { - condition = alltrue([ - for k, v in var.logging_sinks : - v.bq_partitioned_table != true || v.type == "bigquery" - ]) - error_message = "Can only set bq_partitioned_table when type is `bigquery`." - } -} - variable "name" { description = "Folder name." type = string diff --git a/modules/organization/README.md b/modules/organization/README.md index cc602a6f12..1b004aa623 100644 --- a/modules/organization/README.md +++ b/modules/organization/README.md @@ -500,6 +500,7 @@ module "org" { | [outputs.tf](./outputs.tf) | Module outputs. | | | [tags.tf](./tags.tf) | None | google_tags_tag_binding · google_tags_tag_key · google_tags_tag_key_iam_binding · google_tags_tag_value · google_tags_tag_value_iam_binding | | [variables-iam.tf](./variables-iam.tf) | None | | +| [variables-logging.tf](./variables-logging.tf) | None | | | [variables-tags.tf](./variables-tags.tf) | None | | | [variables.tf](./variables.tf) | Module variables. | | | [versions.tf](./versions.tf) | Version pins. | | @@ -508,7 +509,7 @@ module "org" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [organization_id](variables.tf#L155) | Organization id in organizations/nnnnnn format. | string | ✓ | | +| [organization_id](variables.tf#L92) | Organization id in organizations/nnnnnn format. | string | ✓ | | | [contacts](variables.tf#L17) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | map(list(string)) | | {} | | [custom_roles](variables.tf#L24) | Map of role name => list of permissions to create in this project. | map(list(string)) | | {} | | [factories_config](variables.tf#L31) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} | @@ -517,13 +518,13 @@ module "org" { | [iam_bindings](variables-iam.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…})) | | {} | | [iam_bindings_additive](variables-iam.tf#L39) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | | [iam_by_principals](variables-iam.tf#L54) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable. | map(list(string)) | | {} | -| [logging_data_access](variables.tf#L51) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | map(map(list(string))) | | {} | -| [logging_exclusions](variables.tf#L66) | Logging exclusions for this organization in the form {NAME -> FILTER}. | map(string) | | {} | -| [logging_settings](variables.tf#L73) | Default settings for logging resources. | object({…}) | | null | -| [logging_sinks](variables.tf#L83) | Logging sinks to create for the organization. | map(object({…})) | | {} | +| [logging_data_access](variables-logging.tf#L17) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | map(map(list(string))) | | {} | +| [logging_exclusions](variables-logging.tf#L32) | Logging exclusions for this organization in the form {NAME -> FILTER}. | map(string) | | {} | +| [logging_settings](variables-logging.tf#L39) | Default settings for logging resources. | object({…}) | | null | +| [logging_sinks](variables-logging.tf#L49) | Logging sinks to create for the organization. | map(object({…})) | | {} | | [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. | map(object({…})) | | {} | -| [org_policies](variables.tf#L114) | Organization policies applied to this organization keyed by policy name. | map(object({…})) | | {} | -| [org_policy_custom_constraints](variables.tf#L141) | Organization policy custom constraints keyed by constraint name. | map(object({…})) | | {} | +| [org_policies](variables.tf#L51) | Organization policies applied to this organization keyed by policy name. | map(object({…})) | | {} | +| [org_policy_custom_constraints](variables.tf#L78) | Organization policy custom constraints keyed by constraint name. | map(object({…})) | | {} | | [tag_bindings](variables-tags.tf#L45) | Tag bindings for this organization, in key => tag value id format. | map(string) | | {} | | [tags](variables-tags.tf#L52) | Tags by key name. If `id` is provided, key or value creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} | diff --git a/modules/organization/variables-logging.tf b/modules/organization/variables-logging.tf new file mode 100644 index 0000000000..210352f081 --- /dev/null +++ b/modules/organization/variables-logging.tf @@ -0,0 +1,78 @@ +/** + * Copyright 2024 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. + */ + +variable "logging_data_access" { + description = "Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services." + type = map(map(list(string))) + nullable = false + default = {} + validation { + condition = alltrue(flatten([ + for k, v in var.logging_data_access : [ + for kk, vv in v : contains(["DATA_READ", "DATA_WRITE", "ADMIN_READ"], kk) + ] + ])) + error_message = "Log type keys for each service can only be one of 'DATA_READ', 'DATA_WRITE', 'ADMIN_READ'." + } +} + +variable "logging_exclusions" { + description = "Logging exclusions for this organization in the form {NAME -> FILTER}." + type = map(string) + default = {} + nullable = false +} + +variable "logging_settings" { + description = "Default settings for logging resources." + type = object({ + # TODO: add support for CMEK + disable_default_sink = optional(bool) + storage_location = optional(string) + }) + default = null +} + +variable "logging_sinks" { + description = "Logging sinks to create for the organization." + type = 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) + type = string + })) + default = {} + nullable = false + validation { + condition = alltrue([ + for k, v in var.logging_sinks : + contains(["bigquery", "logging", "project", "pubsub", "storage"], v.type) + ]) + error_message = "Type must be one of 'bigquery', 'logging', 'project', 'pubsub', 'storage'." + } + validation { + condition = alltrue([ + for k, v in var.logging_sinks : + v.bq_partitioned_table != true || v.type == "bigquery" + ]) + error_message = "Can only set bq_partitioned_table when type is `bigquery`." + } +} diff --git a/modules/organization/variables.tf b/modules/organization/variables.tf index 4464558e69..146cf9b14a 100644 --- a/modules/organization/variables.tf +++ b/modules/organization/variables.tf @@ -48,69 +48,6 @@ variable "firewall_policy" { default = null } -variable "logging_data_access" { - description = "Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services." - type = map(map(list(string))) - nullable = false - default = {} - validation { - condition = alltrue(flatten([ - for k, v in var.logging_data_access : [ - for kk, vv in v : contains(["DATA_READ", "DATA_WRITE", "ADMIN_READ"], kk) - ] - ])) - error_message = "Log type keys for each service can only be one of 'DATA_READ', 'DATA_WRITE', 'ADMIN_READ'." - } -} - -variable "logging_exclusions" { - description = "Logging exclusions for this organization in the form {NAME -> FILTER}." - type = map(string) - default = {} - nullable = false -} - -variable "logging_settings" { - description = "Default settings for logging resources." - type = object({ - # TODO: add support for CMEK - disable_default_sink = optional(bool) - storage_location = optional(string) - }) - default = null -} - -variable "logging_sinks" { - description = "Logging sinks to create for the organization." - type = 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) - type = string - })) - default = {} - nullable = false - validation { - condition = alltrue([ - for k, v in var.logging_sinks : - contains(["bigquery", "logging", "project", "pubsub", "storage"], v.type) - ]) - error_message = "Type must be one of 'bigquery', 'logging', 'project', 'pubsub', 'storage'." - } - validation { - condition = alltrue([ - for k, v in var.logging_sinks : - v.bq_partitioned_table != true || v.type == "bigquery" - ]) - error_message = "Can only set bq_partitioned_table when type is `bigquery`." - } -} - variable "org_policies" { description = "Organization policies applied to this organization keyed by policy name." type = map(object({ From e4941c27f2afa053a8dd8a839b026963858a1642 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Mon, 13 May 2024 20:18:51 +0200 Subject: [PATCH 26/31] Implement the full IAM interface for tags (#2269) * IAM authoritative bindings in org module * remove extra newline * organization module * project module * tfdoc --- modules/kms/README.md | 6 +- modules/kms/variables.tf | 1 - modules/organization/README.md | 47 ++++- modules/organization/tags.tf | 166 ++++++++++++++---- modules/organization/variables-tags.tf | 80 ++++++++- modules/project/README.md | 8 +- modules/project/tags.tf | 166 ++++++++++++++---- modules/project/variables-tags.tf | 83 ++++++++- tests/modules/organization/examples/tags.yaml | 27 ++- 9 files changed, 492 insertions(+), 92 deletions(-) diff --git a/modules/kms/README.md b/modules/kms/README.md index 4782d8240d..90621bef37 100644 --- a/modules/kms/README.md +++ b/modules/kms/README.md @@ -120,14 +120,14 @@ module "kms" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| | [keyring](variables.tf#L64) | Keyring attributes. | object({…}) | ✓ | | -| [project_id](variables.tf#L115) | Project id where the keyring will be created. | string | ✓ | | +| [project_id](variables.tf#L114) | Project id where the keyring will be created. | string | ✓ | | | [iam](variables.tf#L17) | Keyring IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_bindings](variables.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…})) | | {} | | [iam_bindings_additive](variables.tf#L39) | Keyring individual additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | | [import_job](variables.tf#L54) | Keyring import job attributes. | object({…}) | | null | | [keyring_create](variables.tf#L72) | Set to false to manage keys and IAM bindings in an existing keyring. | bool | | true | -| [keys](variables.tf#L78) | Key names and base attributes. Set attributes to null if not needed. | map(object({…})) | | {} | -| [tag_bindings](variables.tf#L120) | Tag bindings for this keyring, in key => tag value id format. | map(string) | | {} | +| [keys](variables.tf#L78) | Key names and base attributes. Set attributes to null if not needed. | map(object({…})) | | {} | +| [tag_bindings](variables.tf#L119) | Tag bindings for this keyring, in key => tag value id format. | map(string) | | {} | ## Outputs diff --git a/modules/kms/variables.tf b/modules/kms/variables.tf index 2708a7f7cf..d0d335674b 100644 --- a/modules/kms/variables.tf +++ b/modules/kms/variables.tf @@ -87,7 +87,6 @@ variable "keys" { algorithm = string protection_level = optional(string, "SOFTWARE") })) - iam = optional(map(list(string)), {}) iam_bindings = optional(map(object({ members = list(string) diff --git a/modules/organization/README.md b/modules/organization/README.md index 1b004aa623..f0253d11b3 100644 --- a/modules/organization/README.md +++ b/modules/organization/README.md @@ -440,12 +440,44 @@ module "org" { iam = { "roles/resourcemanager.tagAdmin" = ["group:${var.group_email}"] } + iam_bindings = { + viewer = { + role = "roles/resourcemanager.tagViewer" + members = ["group:gcp-support@example.org"] + } + } + iam_bindings_additive = { + user_app1 = { + role = "roles/resourcemanager.tagUser" + member = "group:app1-team@example.org" + } + } values = { - dev = {} + dev = { + iam_bindings_additive = { + user_app2 = { + role = "roles/resourcemanager.tagUser" + member = "group:app2-team@example.org" + } + } + } prod = { description = "Environment: production." iam = { - "roles/resourcemanager.tagViewer" = ["group:${var.group_email}"] + "roles/resourcemanager.tagViewer" = ["group:app1-team@example.org"] + } + iam_bindings = { + admin = { + role = "roles/resourcemanager.tagAdmin" + members = ["group:gcp-support@example.org"] + condition = { + title = "gcp_support" + expression = <<-END + request.time.getHours("Europe/Berlin") <= 9 && + request.time.getHours("Europe/Berlin") >= 17 + END + } + } } } } @@ -455,8 +487,9 @@ module "org" { env-prod = module.org.tag_values["environment/prod"].id } } -# tftest modules=1 resources=6 inventory=tags.yaml e2e serial +# tftest modules=1 resources=10 inventory=tags.yaml ``` + You can also define network tags, through a dedicated variable *network_tags*: @@ -498,7 +531,7 @@ module "org" { | [org-policy-custom-constraints.tf](./org-policy-custom-constraints.tf) | None | google_org_policy_custom_constraint | | [organization-policies.tf](./organization-policies.tf) | Organization-level organization policies. | google_org_policy_policy | | [outputs.tf](./outputs.tf) | Module outputs. | | -| [tags.tf](./tags.tf) | None | google_tags_tag_binding · google_tags_tag_key · google_tags_tag_key_iam_binding · google_tags_tag_value · google_tags_tag_value_iam_binding | +| [tags.tf](./tags.tf) | None | google_tags_tag_binding · google_tags_tag_key · google_tags_tag_key_iam_binding · google_tags_tag_key_iam_member · google_tags_tag_value · google_tags_tag_value_iam_binding · google_tags_tag_value_iam_member | | [variables-iam.tf](./variables-iam.tf) | None | | | [variables-logging.tf](./variables-logging.tf) | None | | | [variables-tags.tf](./variables-tags.tf) | None | | @@ -522,11 +555,11 @@ module "org" { | [logging_exclusions](variables-logging.tf#L32) | Logging exclusions for this organization in the form {NAME -> FILTER}. | map(string) | | {} | | [logging_settings](variables-logging.tf#L39) | Default settings for logging resources. | object({…}) | | null | | [logging_sinks](variables-logging.tf#L49) | Logging sinks to create for the organization. | map(object({…})) | | {} | -| [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. | map(object({…})) | | {} | +| [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. | map(object({…})) | | {} | | [org_policies](variables.tf#L51) | Organization policies applied to this organization keyed by policy name. | map(object({…})) | | {} | | [org_policy_custom_constraints](variables.tf#L78) | Organization policy custom constraints keyed by constraint name. | map(object({…})) | | {} | -| [tag_bindings](variables-tags.tf#L45) | Tag bindings for this organization, in key => tag value id format. | map(string) | | {} | -| [tags](variables-tags.tf#L52) | Tags by key name. If `id` is provided, key or value creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} | +| [tag_bindings](variables-tags.tf#L81) | Tag bindings for this organization, in key => tag value id format. | map(string) | | {} | +| [tags](variables-tags.tf#L88) | Tags by key name. If `id` is provided, key or value creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} | ## Outputs diff --git a/modules/organization/tags.tf b/modules/organization/tags.tf index d25757c243..0321818a5a 100644 --- a/modules/organization/tags.tf +++ b/modules/organization/tags.tf @@ -15,50 +15,96 @@ */ locals { - _tag_values = flatten([ - for tag, attrs in local.tags : [ - for value, value_attrs in attrs.values : { - description = value_attrs.description, - key = "${tag}/${value}" - id = try(value_attrs.id, null) - name = value - roles = keys(value_attrs.iam) - tag = tag - tag_id = attrs.id - tag_network = try(attrs.network, null) != null + _tag_iam = flatten([ + for k, v in local.tags : [ + for role in keys(v.iam) : { + # we cycle on keys here so we don't risk injecting dynamic values + role = role + tag = k + tag_id = v.id } ] ]) - _tag_values_iam = flatten([ - for key, value_attrs in local.tag_values : [ - for role in value_attrs.roles : { - id = value_attrs.id - key = value_attrs.key - name = value_attrs.name + _tag_value_iam = flatten([ + for k, v in local.tag_values : [ + for role in v.roles : { + id = v.id + key = v.key + name = v.name role = role - tag = value_attrs.tag + tag = v.tag } ] ]) - _tags_iam = flatten([ - for tag, attrs in local.tags : [ - for role in keys(attrs.iam) : { - role = role - tag = tag - tag_id = attrs.id + _tag_values = flatten([ + for k, v in local.tags : [ + for vk, vv in v.values : { + description = vv.description, + key = "${k}/${vk}" + iam_bindings = keys(vv.iam_bindings) + iam_bindings_additive = keys(vv.iam_bindings_additive) + id = try(vv.id, null) + name = vk + # we only store keys here so we don't risk injecting dynamic values + roles = keys(vv.iam) + tag = k + tag_id = v.id + tag_network = try(v.network, null) != null } ] ]) - tag_values = { - for t in local._tag_values : t.key => t + tag_iam = { + for t in local._tag_iam : "${t.tag}:${t.role}" => t + } + tag_iam_bindings = merge([ + for k, v in local.tags : { + for bk in keys(v.iam_bindings) : "${k}:${bk}" => { + binding = bk + tag = k + tag_id = v.id + } + } + ]...) + tag_iam_bindings_additive = merge([ + for k, v in local.tags : { + for bk in keys(v.iam_bindings_additive) : "${k}:${bk}" => { + binding = bk + tag = k + tag_id = v.id + } + } + ]...) + tag_value_iam = { + for v in local._tag_value_iam : "${v.key}:${v.role}" => v } - tag_values_iam = { - for t in local._tag_values_iam : "${t.key}:${t.role}" => t + tag_value_iam_bindings = merge([ + for k, v in local.tag_values : { + for bk in v.iam_bindings : "${k}:${bk}" => { + binding = bk + id = v.id + key = k + name = v.name + tag = v.tag + tag_id = v.id + } + } + ]...) + tag_value_iam_bindings_additive = merge([ + for k, v in local.tag_values : { + for bk in v.iam_bindings_additive : "${k}:${bk}" => { + binding = bk + id = v.id + key = k + name = v.name + tag = v.tag + tag_id = v.id + } + } + ]...) + tag_values = { + for v in local._tag_values : v.key => v } tags = merge(var.tags, var.network_tags) - tags_iam = { - for t in local._tags_iam : "${t.tag}:${t.role}" => t - } } # keys @@ -82,7 +128,7 @@ resource "google_tags_tag_key" "default" { } resource "google_tags_tag_key_iam_binding" "default" { - for_each = local.tags_iam + for_each = local.tag_iam tag_key = ( each.value.tag_id == null ? google_tags_tag_key.default[each.value.tag].id @@ -94,6 +140,30 @@ resource "google_tags_tag_key_iam_binding" "default" { ) } +resource "google_tags_tag_key_iam_binding" "bindings" { + for_each = local.tag_iam_bindings + tag_key = ( + each.value.tag_id == null + ? google_tags_tag_key.default[each.value.tag].id + : each.value.tag_id + ) + role = local.tags[each.value.tag]["iam_bindings"][each.value.binding].role + members = ( + local.tags[each.value.tag]["iam_bindings"][each.value.binding].members + ) +} + +resource "google_tags_tag_key_iam_member" "bindings" { + for_each = local.tag_iam_bindings_additive + tag_key = ( + each.value.tag_id == null + ? google_tags_tag_key.default[each.value.tag].id + : each.value.tag_id + ) + role = local.tags[each.value.tag]["iam_bindings_additive"][each.value.binding].role + member = local.tags[each.value.tag]["iam_bindings_additive"][each.value.binding].member +} + # values resource "google_tags_tag_value" "default" { @@ -108,7 +178,7 @@ resource "google_tags_tag_value" "default" { } resource "google_tags_tag_value_iam_binding" "default" { - for_each = local.tag_values_iam + for_each = local.tag_value_iam tag_value = ( each.value.id == null ? google_tags_tag_value.default[each.value.key].id @@ -121,6 +191,36 @@ resource "google_tags_tag_value_iam_binding" "default" { ) } +resource "google_tags_tag_value_iam_binding" "bindings" { + for_each = local.tag_value_iam_bindings + tag_value = ( + each.value.id == null + ? google_tags_tag_value.default[each.value.key].id + : each.value.id + ) + role = ( + local.tags[each.value.tag]["values"][each.value.name]["iam_bindings"][each.value.binding].role + ) + members = ( + local.tags[each.value.tag]["values"][each.value.name]["iam_bindings"][each.value.binding].members + ) +} + +resource "google_tags_tag_value_iam_member" "bindings" { + for_each = local.tag_value_iam_bindings_additive + tag_value = ( + each.value.id == null + ? google_tags_tag_value.default[each.value.key].id + : each.value.id + ) + role = ( + local.tags[each.value.tag]["values"][each.value.name]["iam_bindings_additive"][each.value.binding].role + ) + member = ( + local.tags[each.value.tag]["values"][each.value.name]["iam_bindings_additive"][each.value.binding].member + ) +} + # bindings resource "google_tags_tag_binding" "binding" { diff --git a/modules/organization/variables-tags.tf b/modules/organization/variables-tags.tf index 4688504d57..21a0efe6f4 100644 --- a/modules/organization/variables-tags.tf +++ b/modules/organization/variables-tags.tf @@ -19,11 +19,47 @@ variable "network_tags" { type = map(object({ description = optional(string, "Managed by the Terraform organization module.") iam = optional(map(list(string)), {}) - id = optional(string) - network = string # project_id/vpc_name + 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) + })) + })), {}) + id = optional(string) + network = string # project_id/vpc_name values = optional(map(object({ description = optional(string, "Managed by the Terraform organization module.") 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) + })) + })), {}) })), {}) })) nullable = false @@ -54,11 +90,47 @@ variable "tags" { type = map(object({ description = optional(string, "Managed by the Terraform organization module.") iam = optional(map(list(string)), {}) - id = optional(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) + })) + })), {}) + id = optional(string) values = optional(map(object({ description = optional(string, "Managed by the Terraform organization module.") iam = optional(map(list(string)), {}) - id = optional(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) + })) + })), {}) + id = optional(string) })), {}) })) nullable = false diff --git a/modules/project/README.md b/modules/project/README.md index 75388aefc3..cd92870a47 100644 --- a/modules/project/README.md +++ b/modules/project/README.md @@ -1173,7 +1173,7 @@ module "bucket" { | [quotas.tf](./quotas.tf) | None | google_cloud_quotas_quota_preference | | [service-accounts.tf](./service-accounts.tf) | Service identities and supporting resources. | google_kms_crypto_key_iam_member · google_project_default_service_accounts · google_project_iam_member · google_project_service_identity | | [shared-vpc.tf](./shared-vpc.tf) | Shared VPC project-level configuration. | google_compute_shared_vpc_host_project · google_compute_shared_vpc_service_project · google_compute_subnetwork_iam_member · google_project_iam_member | -| [tags.tf](./tags.tf) | None | google_tags_tag_binding · google_tags_tag_key · google_tags_tag_key_iam_binding · google_tags_tag_value · google_tags_tag_value_iam_binding | +| [tags.tf](./tags.tf) | None | google_tags_tag_binding · google_tags_tag_key · google_tags_tag_key_iam_binding · google_tags_tag_key_iam_member · google_tags_tag_value · google_tags_tag_value_iam_binding · google_tags_tag_value_iam_member | | [variables-iam.tf](./variables-iam.tf) | None | | | [variables-quotas.tf](./variables-quotas.tf) | None | | | [variables-tags.tf](./variables-tags.tf) | None | | @@ -1204,7 +1204,7 @@ module "bucket" { | [logging_exclusions](variables.tf#L108) | Logging exclusions for this project in the form {NAME -> FILTER}. | map(string) | | {} | | [logging_sinks](variables.tf#L115) | Logging sinks to create for this project. | map(object({…})) | | {} | | [metric_scopes](variables.tf#L146) | List of projects that will act as metric scopes for this project. | list(string) | | [] | -| [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. | map(object({…})) | | {} | +| [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. | map(object({…})) | | {} | | [org_policies](variables.tf#L158) | Organization policies applied to this project keyed by policy name. | map(object({…})) | | {} | | [parent](variables.tf#L185) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | string | | null | | [prefix](variables.tf#L195) | Optional prefix used to generate project id and name. | string | | null | @@ -1216,8 +1216,8 @@ module "bucket" { | [shared_vpc_host_config](variables.tf#L235) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | object({…}) | | null | | [shared_vpc_service_config](variables.tf#L244) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | object({…}) | | {…} | | [skip_delete](variables.tf#L272) | Allows the underlying resources to be destroyed without destroying the project itself. | bool | | false | -| [tag_bindings](variables-tags.tf#L45) | Tag bindings for this project, in key => tag value id format. | map(string) | | null | -| [tags](variables-tags.tf#L51) | Tags by key name. If `id` is provided, key or value creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} | +| [tag_bindings](variables-tags.tf#L81) | Tag bindings for this project, in key => tag value id format. | map(string) | | null | +| [tags](variables-tags.tf#L88) | Tags by key name. If `id` is provided, key or value creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} | | [vpc_sc](variables.tf#L278) | VPC-SC configuration for the project, use when `ignore_changes` for resources is set in the VPC-SC module. | object({…}) | | null | ## Outputs diff --git a/modules/project/tags.tf b/modules/project/tags.tf index 4f2a666459..1f9ff378b5 100644 --- a/modules/project/tags.tf +++ b/modules/project/tags.tf @@ -15,50 +15,96 @@ */ locals { - _tag_values = flatten([ - for tag, attrs in local.tags : [ - for value, value_attrs in attrs.values : { - description = value_attrs.description, - key = "${tag}/${value}" - id = try(value_attrs.id, null) - name = value - roles = keys(value_attrs.iam) - tag = tag - tag_id = attrs.id - tag_network = try(attrs.network, null) != null + _tag_iam = flatten([ + for k, v in local.tags : [ + for role in keys(v.iam) : { + # we cycle on keys here so we don't risk injecting dynamic values + role = role + tag = k + tag_id = v.id } ] ]) - _tag_values_iam = flatten([ - for key, value_attrs in local.tag_values : [ - for role in value_attrs.roles : { - id = value_attrs.id - key = value_attrs.key - name = value_attrs.name + _tag_value_iam = flatten([ + for k, v in local.tag_values : [ + for role in v.roles : { + id = v.id + key = v.key + name = v.name role = role - tag = value_attrs.tag + tag = v.tag } ] ]) - _tags_iam = flatten([ - for tag, attrs in local.tags : [ - for role in keys(attrs.iam) : { - role = role - tag = tag - tag_id = attrs.id + _tag_values = flatten([ + for k, v in local.tags : [ + for vk, vv in v.values : { + description = vv.description, + key = "${k}/${vk}" + iam_bindings = keys(vv.iam_bindings) + iam_bindings_additive = keys(vv.iam_bindings_additive) + id = try(vv.id, null) + name = vk + # we only store keys here so we don't risk injecting dynamic values + roles = keys(vv.iam) + tag = k + tag_id = v.id + tag_network = try(v.network, null) != null } ] ]) - tag_values = { - for t in local._tag_values : t.key => t + tag_iam = { + for t in local._tag_iam : "${t.tag}:${t.role}" => t + } + tag_iam_bindings = merge([ + for k, v in local.tags : { + for bk in keys(v.iam_bindings) : "${k}:${bk}" => { + binding = bk + tag = k + tag_id = v.id + } + } + ]...) + tag_iam_bindings_additive = merge([ + for k, v in local.tags : { + for bk in keys(v.iam_bindings_additive) : "${k}:${bk}" => { + binding = bk + tag = k + tag_id = v.id + } + } + ]...) + tag_value_iam = { + for v in local._tag_value_iam : "${v.key}:${v.role}" => v } - tag_values_iam = { - for t in local._tag_values_iam : "${t.key}:${t.role}" => t + tag_value_iam_bindings = merge([ + for k, v in local.tag_values : { + for bk in v.iam_bindings : "${k}:${bk}" => { + binding = bk + id = v.id + key = k + name = v.name + tag = v.tag + tag_id = v.id + } + } + ]...) + tag_value_iam_bindings_additive = merge([ + for k, v in local.tag_values : { + for bk in v.iam_bindings_additive : "${k}:${bk}" => { + binding = bk + id = v.id + key = k + name = v.name + tag = v.tag + tag_id = v.id + } + } + ]...) + tag_values = { + for v in local._tag_values : v.key => v } tags = merge(var.tags, var.network_tags) - tags_iam = { - for t in local._tags_iam : "${t.tag}:${t.role}" => t - } } # keys @@ -82,7 +128,7 @@ resource "google_tags_tag_key" "default" { } resource "google_tags_tag_key_iam_binding" "default" { - for_each = local.tags_iam + for_each = local.tag_iam tag_key = ( each.value.tag_id == null ? google_tags_tag_key.default[each.value.tag].id @@ -94,6 +140,30 @@ resource "google_tags_tag_key_iam_binding" "default" { ) } +resource "google_tags_tag_key_iam_binding" "bindings" { + for_each = local.tag_iam_bindings + tag_key = ( + each.value.tag_id == null + ? google_tags_tag_key.default[each.value.tag].id + : each.value.tag_id + ) + role = local.tags[each.value.tag]["iam_bindings"][each.value.binding].role + members = ( + local.tags[each.value.tag]["iam_bindings"][each.value.binding].members + ) +} + +resource "google_tags_tag_key_iam_member" "bindings" { + for_each = local.tag_iam_bindings_additive + tag_key = ( + each.value.tag_id == null + ? google_tags_tag_key.default[each.value.tag].id + : each.value.tag_id + ) + role = local.tags[each.value.tag]["iam_bindings_additive"][each.value.binding].role + member = local.tags[each.value.tag]["iam_bindings_additive"][each.value.binding].member +} + # values resource "google_tags_tag_value" "default" { @@ -108,7 +178,7 @@ resource "google_tags_tag_value" "default" { } resource "google_tags_tag_value_iam_binding" "default" { - for_each = local.tag_values_iam + for_each = local.tag_value_iam tag_value = ( each.value.id == null ? google_tags_tag_value.default[each.value.key].id @@ -121,6 +191,36 @@ resource "google_tags_tag_value_iam_binding" "default" { ) } +resource "google_tags_tag_value_iam_binding" "bindings" { + for_each = local.tag_value_iam_bindings + tag_value = ( + each.value.id == null + ? google_tags_tag_value.default[each.value.key].id + : each.value.id + ) + role = ( + local.tags[each.value.tag]["values"][each.value.name]["iam_bindings"][each.value.binding].role + ) + members = ( + local.tags[each.value.tag]["values"][each.value.name]["iam_bindings"][each.value.binding].members + ) +} + +resource "google_tags_tag_value_iam_member" "bindings" { + for_each = local.tag_value_iam_bindings_additive + tag_value = ( + each.value.id == null + ? google_tags_tag_value.default[each.value.key].id + : each.value.id + ) + role = ( + local.tags[each.value.tag]["values"][each.value.name]["iam_bindings_additive"][each.value.binding].role + ) + member = ( + local.tags[each.value.tag]["values"][each.value.name]["iam_bindings_additive"][each.value.binding].member + ) +} + # bindings resource "google_tags_tag_binding" "binding" { diff --git a/modules/project/variables-tags.tf b/modules/project/variables-tags.tf index 8914aae6e6..ac73f03fd8 100644 --- a/modules/project/variables-tags.tf +++ b/modules/project/variables-tags.tf @@ -19,11 +19,47 @@ variable "network_tags" { type = map(object({ description = optional(string, "Managed by the Terraform project module.") iam = optional(map(list(string)), {}) - id = optional(string) - network = string # project_id/vpc_name + 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) + })) + })), {}) + id = optional(string) + network = string # project_id/vpc_name values = optional(map(object({ description = optional(string, "Managed by the Terraform project module.") 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) + })) + })), {}) })), {}) })) nullable = false @@ -45,7 +81,8 @@ variable "network_tags" { variable "tag_bindings" { description = "Tag bindings for this project, in key => tag value id format." type = map(string) - default = null + # we need default null here for the project factory module + default = null } variable "tags" { @@ -53,11 +90,47 @@ variable "tags" { type = map(object({ description = optional(string, "Managed by the Terraform project module.") iam = optional(map(list(string)), {}) - id = optional(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) + })) + })), {}) + id = optional(string) values = optional(map(object({ description = optional(string, "Managed by the Terraform project module.") iam = optional(map(list(string)), {}) - id = optional(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) + })) + })), {}) + id = optional(string) })), {}) })) nullable = false diff --git a/tests/modules/organization/examples/tags.yaml b/tests/modules/organization/examples/tags.yaml index bed4b46188..cce3674346 100644 --- a/tests/modules/organization/examples/tags.yaml +++ b/tests/modules/organization/examples/tags.yaml @@ -23,11 +23,20 @@ values: purpose_data: null short_name: environment timeouts: null + module.org.google_tags_tag_key_iam_binding.bindings["environment:viewer"]: + condition: [] + members: + - group:gcp-support@example.org + role: roles/resourcemanager.tagViewer module.org.google_tags_tag_key_iam_binding.default["environment:roles/resourcemanager.tagAdmin"]: condition: [] members: - group:organization-admins@example.org role: roles/resourcemanager.tagAdmin + module.org.google_tags_tag_key_iam_member.bindings["environment:user_app1"]: + condition: [] + member: group:app1-team@example.org + role: roles/resourcemanager.tagUser module.org.google_tags_tag_value.default["environment/dev"]: description: Managed by the Terraform organization module. short_name: dev @@ -36,14 +45,28 @@ values: description: 'Environment: production.' short_name: prod timeouts: null + module.org.google_tags_tag_value_iam_binding.bindings["environment/prod:admin"]: + condition: [] + members: + - group:gcp-support@example.org + role: roles/resourcemanager.tagAdmin module.org.google_tags_tag_value_iam_binding.default["environment/prod:roles/resourcemanager.tagViewer"]: condition: [] members: - - group:organization-admins@example.org + - group:app1-team@example.org role: roles/resourcemanager.tagViewer + module.org.google_tags_tag_value_iam_member.bindings["environment/dev:user_app2"]: + condition: [] + member: group:app2-team@example.org + role: roles/resourcemanager.tagUser counts: google_tags_tag_binding: 1 google_tags_tag_key: 1 - google_tags_tag_key_iam_binding: 1 + google_tags_tag_key_iam_binding: 2 + google_tags_tag_key_iam_member: 1 google_tags_tag_value: 2 + google_tags_tag_value_iam_binding: 2 + google_tags_tag_value_iam_member: 1 + modules: 1 + resources: 10 From 4d1d3c6811228fcfc59b5b80d9c6d988494e55e1 Mon Sep 17 00:00:00 2001 From: simonebruzzechesse <60114646+simonebruzzechesse@users.noreply.github.com> Date: Tue, 14 May 2024 14:45:39 +0200 Subject: [PATCH 27/31] New Bindplane cloud-config-container setup (#2272) * new bindplane cloud-config-container setup --- modules/cloud-config-container/README.md | 1 + .../bindplane/README.md | 95 +++++++++++++ .../bindplane/cloud-config.yaml | 128 ++++++++++++++++++ .../bindplane/images/login.png | Bin 0 -> 158968 bytes .../cloud-config-container/bindplane/main.tf | 48 +++++++ .../bindplane/outputs.tf | 20 +++ .../bindplane/variables.tf | 88 ++++++++++++ .../bindplane/versions.tf | 27 ++++ 8 files changed, 407 insertions(+) create mode 100644 modules/cloud-config-container/bindplane/README.md create mode 100644 modules/cloud-config-container/bindplane/cloud-config.yaml create mode 100644 modules/cloud-config-container/bindplane/images/login.png create mode 100644 modules/cloud-config-container/bindplane/main.tf create mode 100644 modules/cloud-config-container/bindplane/outputs.tf create mode 100644 modules/cloud-config-container/bindplane/variables.tf create mode 100644 modules/cloud-config-container/bindplane/versions.tf diff --git a/modules/cloud-config-container/README.md b/modules/cloud-config-container/README.md index d7017dcb77..b41490040b 100644 --- a/modules/cloud-config-container/README.md +++ b/modules/cloud-config-container/README.md @@ -11,6 +11,7 @@ These modules are designed for several use cases: ## Available modules +- [Bindplane](./bindplane) - [CoreDNS](./coredns) - [MySQL](./mysql) - [Nginx](./nginx) diff --git a/modules/cloud-config-container/bindplane/README.md b/modules/cloud-config-container/bindplane/README.md new file mode 100644 index 0000000000..035722521e --- /dev/null +++ b/modules/cloud-config-container/bindplane/README.md @@ -0,0 +1,95 @@ +# Containerized Bindplane on Container Optimized OS + +This module manages a `cloud-config` configuration that starts a containerized [Bindplane](https://observiq.com/solutions) service on Container Optimized OS, using the official [Bindplane EE image](https://hub.docker.com/r/observiq/bindplane-ee) provided by observIQ and documented in the [official documentation page](https://observiq.com/download) when selecting "Docker" as target platform. + +The resulting `cloud-config` can be customized in a number of ways: + +- a custom Bindplane configuration can be set in Docker compose file available in `/run/bindplane/docker-compose.yml` using the `bindplane_config` variable +- additional files can be passed in via the `files` variable +- a completely custom `cloud-config` can be passed in via the `cloud_config` variable, and additional template variables can be passed in via `config_variables` + +The default instance configuration inserts iptables rules to allow traffic on port 3001. + +Logging and monitoring are enabled via the [Google Cloud Logging agent](https://cloud.google.com/container-optimized-os/docs/how-to/logging) configured for the instance via the `google-logging-enabled` metadata property, and the [Node Problem Detector](https://cloud.google.com/container-optimized-os/docs/how-to/monitoring) service started by default on boot. + +The module renders the generated cloud config in the `cloud_config` output, to be used in instances or instance templates via the `user-data` metadata. + +## Setup + +Please refer to the examples below for a sample terraform code for deploying a Bindplane server on GCP Compute VM with COS. After setting up the terraform code run the following commands: + +```bash +terraform init +terraform apply +``` + +Wait for a couple of minutes for the VM to be bootstrapped then connect via IAP tunnel using the following command (substitute the VM name, project and zone according to your configuration): + +```bash +gcloud compute ssh $VM_NAME --project $PROJECT --zone $ZONE -- -L 3001:127.0.0.1:3001 -N -q -f +``` + +Navigate to http://localhost:3001 to access the Bindplane console, the following login page should be displayed. + +

+ Bindplane Login page +

+ +## Examples + +### Default configuration + +This example will create a `cloud-config` that uses the module's defaults, creating a simple bindplane server with default (latest) docker image versions and setting localhost as remote url (suited only for local development). + +```hcl +module "cos-nginx" { + source = "./fabric/modules/cloud-config-container/bindplane" + password = "secret" +} + +module "vm-nginx-tls" { + source = "./fabric/modules/compute-vm" + project_id = "my-project" + zone = "europe-west8-b" + name = "cos-nginx" + network_interfaces = [{ + network = "default" + subnetwork = "gce" + }] + metadata = { + user-data = module.cos-nginx.cloud_config + google-logging-enabled = true + } + boot_disk = { + initialize_params = { + image = "projects/cos-cloud/global/images/family/cos-stable" + type = "pd-ssd" + size = 10 + } + } + tags = ["http-server", "ssh"] +} +# tftest modules=2 resources=2 +``` + + +## Variables + +| name | description | type | required | default | +|---|---|:---:|:---:|:---:| +| [password](variables.tf#L63) | Default admin user password. | string | ✓ | | +| [bindplane_config](variables.tf#L17) | Bindplane configurations. | object({…}) | | {} | +| [cloud_config](variables.tf#L29) | Cloud config template path. If null default will be used. | string | | null | +| [config_variables](variables.tf#L35) | Additional variables used to render the cloud-config and Nginx templates. | map(any) | | {} | +| [file_defaults](variables.tf#L41) | Default owner and permissions for files. | object({…}) | | {…} | +| [files](variables.tf#L53) | Map of extra files to create on the instance, path as key. Owner and permissions will use defaults if null. | map(object({…})) | | {} | +| [runcmd_post](variables.tf#L68) | Extra commands to run after starting nginx. | list(string) | | [] | +| [runcmd_pre](variables.tf#L74) | Extra commands to run before starting nginx. | list(string) | | [] | +| [users](variables.tf#L80) | List of additional usernames to be created. | list(object({…})) | | […] | + +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| [cloud_config](outputs.tf#L17) | Rendered cloud-config file to be passed as user-data instance metadata. | | + diff --git a/modules/cloud-config-container/bindplane/cloud-config.yaml b/modules/cloud-config-container/bindplane/cloud-config.yaml new file mode 100644 index 0000000000..78f6ea6ec5 --- /dev/null +++ b/modules/cloud-config-container/bindplane/cloud-config.yaml @@ -0,0 +1,128 @@ +#cloud-config + +# Copyright 2024 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 +# +# https://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. + +# https://hub.docker.com/r/nginx/nginx/ +# https://nginx.io/manual/toc/#installation + +users: + - name: bindplane + uid: 2000 + %{ for user in users } + - name: ${user.username} + uid: ${user.uid} + %{ endfor } + +write_files: + - path: /var/lib/docker/daemon.json + permissions: 0644 + owner: root + content: | + { + "live-restore": true, + "storage-driver": "overlay2", + "log-opts": { + "max-size": "1024m" + } + } + + - path: /run/bindplane/docker-compose.yml + permissions: 0644 + owner: root + content: | + version: "3" + + volumes: + bindplane: + prometheus: + + services: + prometheus: + container_name: bindplane-prometheus + restart: always + image: ${bindplane_prometheus_image} + ports: + - "9090:9090" + volumes: + - prometheus:/prometheus + + transform: + container_name: bindplane-transform-agent + restart: always + image: ${bindplane_transform_agent_image} + ports: + - "4568:4568" + + bindplane: + container_name: bindplane-server + restart: always + image: ${bindplane_server_image} + ports: + - "3001:3001" + environment: + - BINDPLANE_USERNAME=admin + - BINDPLANE_PASSWORD=${password} + - BINDPLANE_REMOTE_URL=http://${remote_url}:3001 + - BINDPLANE_SESSION_SECRET=${uuid} + - BINDPLANE_LOG_OUTPUT=stdout + - BINDPLANE_ACCEPT_EULA=true + - BINDPLANE_PROMETHEUS_ENABLE=true + - BINDPLANE_PROMETHEUS_ENABLE_REMOTE=true + - BINDPLANE_PROMETHEUS_HOST=prometheus + - BINDPLANE_PROMETHEUS_PORT=9090 + - BINDPLANE_TRANSFORM_AGENT_ENABLE_REMOTE=true + - BINDPLANE_TRANSFORM_AGENT_REMOTE_AGENTS=transform:4568 + volumes: + - bindplane:/data + depends_on: + - prometheus + - transform + + # bindplane container service + - path: /etc/systemd/system/bindplane.service + permissions: 0644 + owner: root + content: | + [Unit] + Description=Start bindplane containers + After=gcr-online.target docker.socket + Wants=gcr-online.target docker.socket docker-events-collector.service + [Service] + Environment="HOME=/home/bindplane" + ExecStartPre=/usr/bin/docker-credential-gcr configure-docker + ExecStart=/usr/bin/docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v /var:/var -v /run:/run -w=/var cryptopants/docker-compose-gcr -f /run/bindplane/docker-compose.yml up + ExecStop=/usr/bin/docker rm -f $(docker ps -a -q) + + %{ for path, data in files } + - path: ${path} + owner: ${lookup(data, "owner", "root")} + permissions: ${lookup(data, "permissions", "0644")} + content: | + ${indent(6, data.content)} + %{ endfor } + +bootcmd: + - systemctl start node-problem-detector + +runcmd: +%{ for cmd in runcmd_pre ~} + - ${cmd} +%{ endfor ~} + - iptables -I INPUT 1 -p tcp -m tcp --dport 3001 -m state --state NEW,ESTABLISHED -j ACCEPT + - systemctl daemon-reload + - systemctl start bindplane +%{ for cmd in runcmd_post ~} + - ${cmd} +%{ endfor ~} diff --git a/modules/cloud-config-container/bindplane/images/login.png b/modules/cloud-config-container/bindplane/images/login.png new file mode 100644 index 0000000000000000000000000000000000000000..3de1e6829ab26c97c9bf8214580ddf3b22987bca GIT binary patch literal 158968 zcmXt?+I=rd>7 z-(TSPyR$GRr}uX`>t|tj?+k$=x^(8ujWdt#-?a*U&T76iT>c+Kd>>QHcKv(l4;~)C zh3hYc=H8X2piHEEH?Mue=DgC(!3OsuZyDSv&&|F%`I&)~Mf?-DX!TEkQM9qYvk<>h z_H{+TbS#JYqPlSV2N9{!J3sC&M*Dr9Y(A|$h3lPe<7+3q*Zmrvoe(~NPERJ+BWOFe zLr;%>h1nDT?EX?xk~y^_`Z&~whwQwsYI`1t@pJE>DlT+j{Ilg_VrDa=X#k1!zMW_q@Gdy0L^wS(4#IerE`$}=lw~Q z;p|A8L~JT?Z8&_f3$;~N9g`&ab9ZBIfA)-P47GchyGz>khWUR4)0xunZ(deX&y*@>is}bkB6iy&5N z`coV=L5vT1oDnpj0}yVk(tH-wxFR1pvuu#z#~TE}IOkbc2US`i+T5npXh2&lp1_@% z%OM%5SWU1hP#Nmm~?wVY74@y^bI=jE(y$t1=6>9wzSo~US;oNNC8yf-p zEVTPvm*e1>@3nVtoq9y~h4+7ivr?in&YHP~aT4|$q_-dn)-GXFAdMTA9qQT3bt;OtkvIiiw-2^ZB@Oy$V)~Z3okCsszc|FhH1UJS zc>i)Ak{wjtsH>OnclXuOSwzGmPH!x1g_Mwf3DIn+1ZQ<1 zI`@m6gR0CBZ;TZoS(lXnn5%pcA?oIX>G&{UhSS{Tg33t~(|}v^dC5QYo%=!e=TiG< zTb)C>p-9;?{C} z6}hClX?`|a-X_>O*3eF3fPizZ@S)7zbFqiWj*1I|%YR!vh=24t`4ZAa{El2xDTj2p z*YU)E1JvfV+8mng9zJoXry)tAF73zP2NRbJlc}EXp*jdUxpQ0-J2tn;zVam2ff$c1 zA}BIeB;7~$iE6yi3_TdW^zg%o^~rjd$e}7uEC=tgt?(75+Zb%O-H={uSzHZU95du$ ze7iNFd3jUtiA_L3vdB)T3r%4X8~TBOJDlCZRa039?kja2(%OxTQ6owpff_Invg7>F z6&`Dg-3?Oq7~!NJ;yGXB2#m;z;+1>o_qv^}f6zvwnV(}437KH~`s~yQ(#{nw|y(7cmt|}cXh-g+%1+Fojd;Ief%TcH%1L~}Pz^KGSVa1Hws@y>bAN(19Lfh|KGdzS_N_8ee z1;Z2?w=z1Q55%o>x@pRv#awYks2U}|m>ih(EII&MO?Jm$Dk#6OgKRR<&C)|D!-++X zR!a-ZLM>~)u!H;5DELaH{;vPE{srIl>Os>luErv1*jvgF5vl$Q$$ zIoy}D>f`p}2k3@QAdUv1B$fO*&XEM}RILUv_Y~}tQg$adawLGFDoUxuxtnJ4Z@vo9 z?g|8WIqAj91HMXK6RVTu9&jsX8hWHVjo#h7H%_D8goFX>YmV<64K9A%Cw59^YWOIM z6l&(vHdC=*331Yy^wc+)@YT1~#lJNAFU{$7krj4@4IwRlye=&k;O;$nyA55u9qa2L zUB#y_LH;%OsmOVY4v^|lAGp)^Th3Xv9shLS&wk1U0SO8@OtT<`b5yeP&j^8~yef%n zTa{^-wl2)_z5?Ez&Q9vM9#-#gz2ykHL`~6$7z6Se9H%LGXG}8+k@$!Lo{%F7hcNe3 z|6J+9I>v7fj?S=3U;P%UAMKWpMG$V_(B|j)hfpgCP_*~{A7R!y%Nqq(yx>7b$`%?E zh`s~$Bss>7q^6`Nr0W#@F7)z6dAx=#P!NtSF;w9l|M4`z1><@>F%#SV((lWaR>E&+ zecZ0KTK1np-#4By3-`5M+(${v+;d7@oO73Ma+3a=2M%1StLcx5Z8UF$tr{H3Y7bW0 zA3VMiKzbn~!~Xzj7i$ZdtTfFoa1LH~EIe#6&MFBG)W|ZALfyal7rOLJs1s6`m^SEgx}2Z*SaNjjb;RWpwBWe1&$1E3Q`!2`>cxncK>V z!1*|&*w=(I+%EN%Zpf~Qgs8B5@Ta43XgE;a(OC0;>Q-xm4DnxO*B1w0RBfMKUY(ub zMq9n`Iwxd*BXTQEO3@=v-;@xPrx%2hzt6<=ZORXTAMR`NUpM}D$DB9_Ey_!DS$xwV zd8b>;9MIeFDEiUWh5KixdURWEjE3JmAZ5zXK6yV(5WX?Bq}{gaEKxUQK#}b1&FX!8 z)R8rus>9Bb=4C|w{xxv9%za_40f-3yphv#VfAL>Ct%)}8Dy3^(2?YcX;EGi>+*wur zDpxQht+WR#O)H{JEbZO6E!K8ludqTO#NpUs*s#&i>qa!A=|WT6X{wGg2UF8A`&qmy zi4HIkFp!S6e)GB)b96Tv;r%ud>C;$bG@cr09y+7hl23n{HteQE33+f&$Qvf+e`20hjNY8dffJ&~8e6SOO%ZJ;9O>6?Xo(XOr zhi2%Z!;-$1W#fqwPOVt;oMyPnIQ_ccStj?!RJK9wRs zV3Xj^W0aQQes`u!fvLJHbKP)22OL zI{4+V;SpOeXZ5jt{|(2(B6U4rSQCjHYn}r)b zm7R*uMd9A2V=xg*YP!S4K3c~4OLpf#GFvS?1|8n(eb)`oK_TZ{=loWNbOTfd5Qp*+ zyge`nN1s`t(YOiq$o~nglLh%640TEHH19XzU-^3Y;2p!?h+SQ}SE(6iuX@9bX5$5C z=aBu>`mP{xMVx4Ug7@S~b@-|lRtoFB*Bu66`K`?^m*CQIFEt z)Z80veTEW9oo-L9e0V^cOZjmrz_0$pahpnIAP;~2MtrhZ3&mIV)H+56Z~s!y#eNsa zIKN)JHQJkeR7#RcPu@0j(C&YCB{ZXitG^mX-Ug|ohr}okU>s#o{cu9wvp=N00@kTb zQeRc@?am1dcbED5ftPx*VgT3RGpY4GCuq-M%zC44fOao}*<=1&YwQgZqSlXO(*N*o zk2Dd26^<@#k5ljerrX3cSQuH5c{g>MrpJHjWdu(A>_Oq$t9I zf}(zy{YIkU1lpfVy9J_Ji$Prx-PBq>a)oHYu6J})mEc~OSX8-fOjQnbPUtBT$SeG+ z3aRzWBYMubqP|n+x@O8Ty+%T(@}GK<$fr&s_)9Qp$H?kmK~gPNtPQgZM_GfdGe!Je z)2!ptRG7EAF4PpXd2bFciIj2_yA17(hQBOi)l<}-t{I>6MEm$kUwf36#xlA`B`TY9 zHA-!4wv;-#;%v1T^l+tH|8V59YZ@XE_AY!1O+v>AR~P1TgR~f)|1~&9j4Twx{T%B2 z+emw&tIi_O4AFT$FIk9PT<_CA|2~`(n_RhW2fcurTc-z42a}G#pH&PG#Gvn|=8B|M zWZCm==Yag*kuSFIJaCAl-tk2+Y#c43_h0Wic$B-{b=CMOy=_-V02*IFGl~S}@5;C8aXOLtsJMe(zLj&4(gR}b`_`A<*>W}1{m2|w<)2k_G(!C?>#CcjiyZY0{rQgJ> zESh^vFDy6RX@Ez>HV7$K2amUR4p4M#TO#;dYU4WmNli(HIO3AyeY_=cdDl#x5jyXd z&X{()UEz1YB1(|&cF(0cbPmh|_ykBm1Dp_wR^4!QS0pk(EyW&6R1q;G?A{=|)~mDt zb>mj5_wP)^RK$vD9y)Jr{uzLZ^$zJ_Lgu9 z68U_a@5lNfJq0JX^Y>tZasGKO&PtX)<)SNaM?jy19kHCNJ|EVwLcae5y)(IaO{9fQ zd&CedN|T+om6vCi_%*onE?VN6%85oFebdRX!q}DL_IXk6>tgj-P&wmRDAc6^aU!=7 zSXgit&WXgTk$Y-Te^iYvHC>&Pnbv7H1mhL+i5H+M-TPSrcLQ$q<9&hssmPZ_rqUxq zS{qIH-g}NSlmQPrft68-RomW9mGTP(ePF1>`(xxLy%}jsP}h8|n#||*Dx+JN6y4eB zb-RE0$u$}_vI^$0AgsE`xPc2ZdOkPAB~ue2+9GEMVabY=UjMLVIhTZ!6L4jmP^X)= z)RuF4WA@t+zsg0tl%w}jqj0Hwn&C;c$XevroIkiGxJXTuR;|uj;wf21PKb#uL3hA~ zQd#Ub)2qKpqT465()->0eGphT7BUiY8>;5dV?V%}4^HcJ`g`J@$WP0~7)7!`oU_XM z299~@6Q2`)yA>CYop-JJ;1SFk?{)4zrs$f`s^E17-C7K#gs}j7MClsL z^lhFG23lM4ojhW9oY4TF<5pgtg3=AeJV;wt&q1r;3SRsV&z{4_z*hJIv&k0JdNsGMk z9Ic^&*laW6p+`w|8zLN|y+sWng9FR&aDL1kF8DoOqzd7aB415|IX(FHSF=4A?WC$l z^x~vc;}Kybc6VlMq%U9Ztb@orAy>f;)rAZ-cQ*l)UnDrsy53w(HA74}3-bgw-_iS)-n(Q^x28&> zC|Q9h(b59UKFirM=^H)|hp{rn^=8b;(;!J5OD+h_v3}vKoR( zpzlxw(g5LEVhGXG)U>LFZHmO!q74-zg)ZeV0jc@1&ynYsX{wlpQar?P7bUaD)6u`7PATz`cN#%5Rp_9p@je0$<;W(Bt8V*DNt| zG+eLu8E+woz)FR%3!_-ECXRJ z6R-5w919=XEz`Bm?7o^Gb0kYy8VTGr0Ev0`lDw zj^y*xsA?pT0KeCuWQE^ti{Ogg_0gKAz&v~8piv-+|9$g2I0-fJG#8U&r?2yK&)2oe z-&jY558`RI3#l^?cywStqaR0)KSb{IJ96orEeyW**%V4aZew*+pF!b{5#=KvPOr|3 zOV69-8&x2WTpC!cep66AyO`tvFXDVbd##gyEr(v~L^)uWH zwwdIY@+TT&xbz@Q>i4SSD^ThQ(i}j$0u64K@_m`Ltd#^{%sWo1J+wUu*ovJquNs8Q zYq!x{x3A6l@AmrK{83bWwqUSotn8-I3724_x{h-JQLwkDBNRp}3*r(~EzlD2BUBUq zdO-(?n}wBRhd1ely%lZ-aUuW|wt>kix-J%5UlrA}?qfuM?9kj1oUw=Y8&?>7@um8| z%yeEEG1_|1&_U6LYdNZB-=ciTk=p%iE_GT&;H#z0TxE_Er8_TarH=!%f5&4cl$5P1 z9rOKhcj~X2tp}g9$Wgyd5>|L|kX4sftIQ4mp|0R$2b57-SfJL<#a6|uRufXaGh;u%qyREf0N zQ*3I`>Y2q^8Ft84nUd@5n>EnmrHV6`@BXJTt#atVpL=rwA1*6+WO>tkbm{=ehP~&I2=4JmV zlz!hFRS|7*_)gty?UdgQsw41gcraN@c?n9fHNlxj3Fpbt$qyDdAK-oXBEQn-JY57I zoVmObCr?R8aHQy#@eV>}+XBMY_O~L^J8Q}w- zU122f&u5z-9W>Pd4=Q+KhQNWbB*uKydlXFWCX76VO_;;_+v zJohoo!hG(EhRsalOY!lY4QQABh_ki6ocOC+&m1_4z>8vOW$lsK&rpbfvxH0Jgg!Z0xMwdG6D9BZ&WA; z?+`p)@C-6G%Iwf(MVoJRM-LuyTDNDWt3LrO&O+eNmUnaqjm{qNn*(Dwqk z_?WN?hyK{@hX=6ulgQ%uu#Ut$`8gp)ZR|8~ak}Ng1%Vu^X_39s*MlbaF5d-TDFk-p zp8w2dN-ZVTkCw{G)ZoSJtzL=6P6=!wTEoU3M8B_5lF{tzZX^F&{5^UP%azOK^6ypN zaIqraZs9+l?vzOJa$P=9%M{q_s^yvmMJjt&KAgGZ$*Lg8S&+eYOIpkYo0X)v&_fZQ)auCzA1F}fyq7+{xJIBrUwgX1M+hrmOg()y? zc`es!&@)Y8kOp=5Yc+hnSa@Or{4!4w>T|;Ca=Vs_xZTMSD}nx%Bwy(jYz?zi>8whp z%8;QLU7G^cU)?Q#ZN#W(9hT)ho+={tQm?BT!1oQcJb;{X23P#%(o;4F2)4dv@8L9V zO4WiqHZu*d+uY^ik;+k zd-lS!p!XuGNEkek^H&4+_(WtWjNdD3&1v4;==bT3UER@DMpoQ!>3=%Ep?hF==w=@O zLSe4R%<{Q5XsG6J`3vq^xGTpW!av}f~M*Hmom(yOG ze&o7!~-f}0(<%uN!9aLBsFX_szzD#^g`-AbQo?NaH+zMa{{k#c! zE689TT`{`5?42p7qWH{y#uv6euexxaVG!qs_Jb(Oi{P>$y37FE=QnP}A!XfRWHcNW zNbC!c?VOr+FpshN>!8vLpB((J6OnP0y$v{pL_%ucI#qSt+4+38D?2^$;7aSazVKVj zt7}g=PYGSuGciMEpl-A_D+}QyD4%_)$zUmOM;y3oUNyg=(Z&IEM*2p3SdsTb66_EZK7hyqMVD~U%n=% z4CDi75Q>~|LnK%I<#?Q*K*1#SlMqPm#lj2g?z9;FXf=&6uc?6xTvx#=LN_uLK>tfT zlXwc{v7QrJFy5PG|A&C@?Q3g!msrvG@4yB2Td&wmvo>r8UfiQBPrss{*AP@a@8!-I z?p=LVrPwSVb)=|aK|H19yBC>V&OvJ_lm9JKB_S% zL{L?$43wWTkpo&H1P}!dH!sKR5PZlp%Ct4ST+gl>7#$3~7-XY_%;`=z%DHa7d>F`ci@bpk>q* zH6+?;!uuE!Q2~0JBupw>yAD6<`J>Osb&u_^AnC|x^mT5gb?c5|8Bx0)G{!3)-~8oqHWO-V*P53~U+AM3CphOhGO;zR?HHd+j~1H93osw}SkG zoh{of$iRpww%F50dogG2ZVK{W{HjY?e5-g~Ia}E&)u?s3s@tf7`@Zyv*{!`r@LJ3p zYImWur*TiiR$~Aqk_zTxVQuhS1=2EPN)BpJ^JD4`y&n47`A0 z@~@y^kx;8t$@q0U7U#Et$-9B3PLOOem{nQQ5khf@Wc$(mRT@HFG4Y~2F|@MMZRi*T zTT@?CbcOF)X)5Oj`Wnt$rD^WsU2+vN*gRw-xPwC&EQMpE*V6vzQC~uog{Q&G%Q^&56>le5n z+3L1qqueuiOI{j3jnab@%0KwT=pzu&2M@*g8;l+W{joVBmiWQ03_Z+bS9{>5+AKP4 zbxw)B25FwYyUqzx$O=(ny2QAAL{mquheSZR^bmJQ#q8kj^3NgRKO<;0 zCg<)07a3T=(lSBr!&I&9&pi5mne)s(;^P<{%Pb~L3Ky+UzsHI2%2dV$5pOyLT3cS7 zYyeg_dZ%r#lkub z2e9tJfgCX{aQ$VV>J+6f1_3#63S4*7*^;PLXF%v5$3!Zif5V@C>P?isKuu2#B4NGq zrDu!pZPx5VL9I^fjCDYsc&OgGUX49`^&5u?A;%)xO4t)dR{A&j2JT^us`a=?`;{%( zw$54;0~vj&sWC6dCzM@-d-gsYj;MnF@(=Kv9?7sh__y(^{y8mnnUdD(A}NCl*RV6# z-$6X?9NBgSRxN{fUe7TcJHgc6g1&2*6^fDr{~p4cV@H1uQqS@&bW?{Z_(emiJ|tCd z%QK<7ieaA%`q(2w(|XbAIv#}dH!F3%|%S)fQYHDispMO$vbo=ypwEkU=yH(t;zlkyhCUZ|>=bP6F5fgeZ zh(()=Si5LXZoW!^)>)q{Z=mUFfVdo{Y%?fR>(HvYOxtkWl4!PCKOYKlhj~?E_B~Kl z_<84(Ah24od|&8d(8n^Ef3^Rz0W=j)3Gy|^q*M9kg1&Onvijf<1!wi8zSywR?cf4D zpLV7;W|aQ-9uLwK%7)%27O*Vc@8wfMc*m>nA@BZt!o|VZ?=|4~K9K32=nOT`Zse-Q z_qyb{sv9&Sqnwz%4=Qo-&%V;Wpw{IHwcUM;biH*5*L(su1Q(qU_CaXE^h*V?=?b+VMF&;b1CFqrYr9GShP;a*gFtKoQ2MvG#gy!u24HT3hfF z=JB9!lHcn&^C)?3@Rz_!43XEz&OxMXjKCT$S0YXg(9SxCDwC=QgiU=A9s9+;WQVZb zh8p;IOfAU)5-uvrX#)t{XYMGTLc|7@*NkBOm5&@M)4@dPlYhe@G{AAY5WRe^z__FU zS}-HzzM4aYWF8C%--vRN2oXEXs@;N*#?XSJNY(_N0eMkzS=gP^wLem}mc2%G3a9cq zd%Kftq)m#tzR1s4Yx%ul6*%tMNdA=aXP3>7PW$RqLdks#K6**5tU25PyF_ujja!~Q zCsUMC0W56ZF)bM_1qq!@c-5c(hkB-H)$m`KrtN~uSe_)d`-y3SW1kU6T-wQf^@)dAuyXIpsCBUt9U)LZ2@?`fg9r_?bHAa#R-Fe<1`P zbnfa-Vl7LF^2>Nq_%)@$YOeuixuw~>mmu(yQ5(YY1*9(x*JS5a>II+5OZGptcln5N zs>%~|cj-NDQ@5_VULkp{;n*rPMnE(bF#Fj1V66g%R}RqCPk0c@XYH!HVb}0=)cXvpM^z{CHh7b z`GBx?p-B=wb83AJJR^v0?FfceRa&27y;37;U{lFe-{9z`SDLcgj$&4+zPA>hM1A=& z0h2iLV4!FHOz%_`R0sd%dC>knvFOn9)T{Pp2ZY1CW}sw?aslE=_YA@l;%*q8dNQ?B zAjIB4))`Fg3j~G)El+*Zn%%}UuSm5x>ef_=X#ek9;=&-m35LN^cQTufyMNxqwTGy8 z#AY)m+hTe}@Vr!pB}Ca*veP-mU3>O!V`<^){tJTr`s8Q~r*xb9z-L)#ZIX^M?r@+w?-)Wx(=exu@7y z<+)#jinkePB&A?rfh$a)Me6ehR*xiRs^+up>Kx;SN8wi1-d)=dMPhq3VpLnK^@k_Q zUJxDbgX%E|eb_Gb7TOOGj^}`mf;7>0O+Jjg!DxmBR2n-x86BklOp^F8<&(Ovv8Ri_ zG}XD2>sQ!7QK=y*(sdLofF>U}vR(?#e}1X8o0^>NM82@#{WGrz-jshZ+}QL@e+rq#kdODG2OJP7kM!6-$P>lrq+I+rp5gl+3h(qRMeph_h)RjD)z>_^ zhD_^9193l;eYO~ykOCgDQHQ+5+H~Ew;BkHMwsBX_*)F5G7cj*I^nr{6a^p|7(@f-kQA&P*=Iu>0j~tVo>C_Q%{bP%`4opPnkLb4N$=!B987 z7vCek9uqR^iFf6InN)zj0w3z(KQlm$mxw*<1xP~x$0boc z%weoL!r9Eh4k|8_9y)P_Zh|atZx+_Z*fv^c>MwuM1g6{C^>Y~6;IK9iY_BGgSE``5 zaGk&|D%Unh&`ct>(Z;ev$$jG=M16XJLj|umBho{!HWjNQHi2o)`8A#QK=`XmfnU?c zjYpW6mTApCgi5C^ZFY@HjQ@k%GAKu6Gjd-H@WVBb|55*y=oc3-sN%`aa%p4teGpFLmR<`3h6DaQ#(%~EaG@d4%Z`kyw*#3ln0Hh((6#d{$v zLojDmG^;&35KJ^!Z`*W;T8pm^p6pHML7SOpfs7pB=is25;T)6Z!#+=5>9I)&`ZQIf zpSi$YHK{Z4@wKLWT#`b7Gea$@^2M;GFfrKmZf_v3**%}T^=#9q2Ofw5-iJ;_T((vU zHABN*9ugd{6KBOt?zCwUL{a(_p z{p@Sl{Mk`)wxi=i_*i>vm4Se9S4CToN8x2%UdJ|28DnBDUVck-FIbQ1@slu+PJAUmn__fJB%at)|&sZn+w$7sCk;p<5PY3D$I4i zL#3iJv5NQ9dZ+yQht1*hdB%jtUU!O!Mm@1D-8EN(Tug=#?fcd|AOHIJhg>NB5&eS% zC#97VpW7M%z|rCECmHcJG*w|u@IY8Sj3*?+6r?vi@9t^k@Lt4*5t4CY6omR_EZCie zy29S)TKX6UMf{BlcRR){!OY)_KRuy*R_Oaph@ryZ_l>Pi+eE=ClTG)VjU6h#?=BqU z=OG7wQ$a0YP0fmu24YeeRDbL4O$9+J*RU{5<$(CGN=m3pbR1h5h_M#nEa6wT5qsr- zWOXA>-6AFEW-A*A{!D>w9Hy%5KT8RJdtS#L_glPr!%`u~PRbUd?V6~~Z;5h#Au6w2 z0FId(y6~U158uwGbb4A0@_(|&h#33bM)qR`b(>6MSFfL!N2Q0dUu;BjEnL>SCxh(d zvKQUt%fX&a1-wF@Pckb#HdpU`7~3#E>%fgkc_t$=FG=N}*A)12r1VDY?ZQ z(W_R&SSix-;}HPPSQ2DBwt2l=V5NRh@RYtP)L`(szB8m&&=bd?(!Un_1wDz=6Vhi+ zwMV}fV}$w)a|b~@w%qLwYJR^YBVt?Gs~%1&M;zu#b13jcdHF1FoZm0ktq z=*SUW)PL|g`?CLWNAKl3))T+JS@y=;G^yefgRuprRPNFZf^ zp`3mNk-44b2buBY%%bXs6Sku87ylfrBfm5NYl4Fg%iI5b5N;Q-Jo{eyyKp-yLMsqE z;_s~YWipUrrs<$izp}5nU`syXYScnhS(m6qy0FZf@Jx8DK9@e=q`ReuVoej(^FhR| zKrn_!pmCIWp?fGn`#4`>=M~_|W$@qZ zM}<1`LaUwZu{%g5?yQ+JU_SW_(3{HR>>i#sv!qldKwbvkg>tlaM;u)893 zL5|X&bnoj-x$_VKihv1339|FvsUphWG20AIh@1$c9o`j?H{2iz@7w4u2=VtOb>TXq zPw)vV#^IZdgK2q$V90~cwD(C(g*^uA;QyxpLk_#aF*Kd?T_nJFCl*1WzBYNX>cdZ3HCb(opoWscRi7)5 zm%T|B;jJv~`EtVN>H@0aadpGb+L*0GCYS9KO!vdD`72GhvYc_R^=W>jikr@Yb7m0a zIKcBq*-p(td|2SoRg`;)J+#8ZF7Vn^NqC35D6b3bqUY14_tc-xHD7{VIaBj4Y52mtq_(A?*1pS+!%B2&Io}pT4TVR+v+xfC^4<`} zjpIbm=1cC{eP61~yd=Gz#Z#}jZ%6;UB{(^yQ&F9Gn0?MsEFFyUiQ89KM{wY*;?AUT_Xx3m+~^K~6P6 zf6lPN@~n7$Sgw&4@DCD=M1lQMw}&!{4Lg1Y`TDeKv8yukAV5U!?CD^b*j<;R(`)9G1NN0D27e$v@g~1cC0ZTNoapgeIZHIG{bMGeYW9R_wg5R_YEKDmZ45qIMn*(y?Zq7^l##5cn*>!6aQ6_ zVuyrQt^2UKK-~ez#h<0MszDUOh*jXsxMd5Au{dCxATsestB$Zc=se&CcY$Ymrrg$q zTOGQbz;Fdp-+|SxbxS|%2t&b2H=Qz*6T&bJal!3shV(IqTkFNjX#wWJgK9PtG2wly zY1HJm5Ne;-AIE{YLn73$OwJKw$rk(`azZ^s}jGOX>HD~__ zu6<2hPcOPM79UZo)JbB4#~GKX)K^IO1)x3!zjPeYQ+(b4{*oROXN2Wj?W>#co6=SJ zpUY`+3MN8$UARELN%vyMMEKmlfS}DXgkSg@&Ae6C5zXQ3yyju}`r}RraKH%OMi1Zy zEF+h5S7g=4xf!!{`l9dg)P+jmWv0}+M_QUhP^vtODhP+OC*jTYC8XQLx3zLd4x5wo zP&1WsNmTj1^)T-tiw2nB}bHx;L_3ALQ5d%YKM0AWy zNESaOhs@+R^*)q75{+nobGE8dnvV>eT95M#w%@z-L+o?Wn%I$*gn(68>G5pbzm;cH zlAJP`_w5#JM~cF%0zs}3bD^{<`arJJ&+9I! zYwlvU>xxkb9Yb#WXN{T%kaCAS6$2k0C*7yrRvM`|%>`XTXK=+nUq4b6Y=Np~wA zNY!giKMxkq*;r*EehkcSLE;0v*p* z#XieoMAx>SPD+4-)%MY8GFn+7|2jgc?AXbrx7pB0t4Q+&>#A;I?%*|bb{Vhlc6WR3 zNXr+>Dw}8@I0aBHuH}P%-1Kvi4HDbaZ?h|)GpFVlY)x=I8pv{1u*AJ+H zndv)B4N^{x52l{vm@d6ms0W3uTQTeu&B2-LuS zqopb7`fTLl9=-&5R2c<5NDWb!)|%&a`w*6xwCCmGkWuM0y3J}t)kc83MR6h3?|NV`O+v}7^6_*VCb~)tg6^4CZzO@1L>%w!DLZv|#!S--& z4iCla+!uhVQfAH%66O}}k>OYdBT2olZLi2!R)4e&lTKG9NP+-)TbJ*bFO7~x^BqFE z(ehuk!PdL_wgNApEsKkDBcKTIWX}JTE2mG4Nj0_cJ^2&;AHO`swc*pZuLz1B^{qEa z@I5B>+>nEue@LevI5ZLB1Z`jfUiVdc^PgJqoQVOG0t{vabQ|tJ@BYIF z@Bm3cBfn#xXu=^O?bXiUc>*NF)~>?Y|8JKJkNd}csI_q&$HEe}-KpXkVvyq(x>iT> zB7}NFL>fIbe$>}^%39rDn`^FOqi_q?c}=^qFM?nW3;Oxo>q+cQVM-8@yUQuz^x5z(hnn766S614sucdcRWr%BYeP+ewu*8JWn`cjOslfV*H)^sOme2*=5$K zPJq=Z(}d8DM{?x*pV%Z7uW@bI-C?e{StKRK#}Cf z^n3<9CI2q^9u)V#4-umksOi`#yKg?(mT6bCr@F<+53Q$x$NsULDh~zty1=9-f-8L} zNPa2I|6tkii< z))SNC=bSwi!v1!ZyoFpj<|x7TzLfr=!?0CJcuJ6cSaa1`Up%S$I; z`x23cfty2{DFazedq`107D(pjlwAa>3I%@imrsgB9^kKS=L2~EQYFq^5h{Dtcjfyr z2QpegyNNA?_4iTOlQ+z8PhFSIqm;_K-Nx=f9Obzz3KM-cS|WigFzdR}#<0JDo{@?!}a(L%dNzPO%#~j~d&Pt_&9AXZIIj=Xu zoDVZ3l*3eV9+Gl4hlM%L`D`jNa^8%bHrsNTIsAM-x7+Vecs-x5=XJbakE_7l=;G>f z&W{9^`fF*sCZL;W$qJFl+_nZadloWWiTK(g_y4`JU}`kd4hCUWAQ+njMg zCgTOy3MK@TJ|}{VmA3UulAeuI-#)h_fk21qYRmqQwg&HZ9?g}Nl#4t)2pO}@ZFHCs zk2qZ);8okl0Iu3}P@LriqCBvSe`hwwDlzz4@Ifv+KE19yh^9hkAl4)Xxyrvns4~oj9b`aq-Zqb>-h}u`Gg_o0}F@&XKbR z2*n>8P;Vywo`?l)qIMhI+K^IW|}pF--_0=fd3QX|)$C2!MA{W!su>H4M( zlzdOo!_2|`_-Rnj(&rhO?ywB3S7RdHiu#Pi;uxaQzTLjpMyYGV9HkgA7ck*8?sxvY zmz~pjLLFYd(+bM?_Tc-pNB=y;OT7ds_eN%~!o^~3r;c(_@pT{YN!t~6A( zqoaqqs2}`XZ@NdgTgsu-5E*c0;c8H-O-28pMBz$k(CcS|4!zCIC|ZZ0V`K2>hFvS2 zA+J#<4z-=RRV>IMTi2mx4RNQAGV1T!H1?+t991-xpnH?s@KZPGVVfauc!uKp=f@ap zkMb>h$tIFDc@R?Y2E=8kH|Dp|1oNB>#+ojPkPg9*5|k$8Y?ADKJE)WY1q&8X+T(Qx zCThML=+Bcq13=?ed1OESJ!t1uWq9h6tq{a5loXnth#rx$IoA!ccac;mka(VMmt3?t2B?-pW9^2d#K$v(fJ9l3iT8j^D`5G1X6emcP<9tCy;Oxu+Z` zhIJVC3fu1`j=%k3x+f)Fx%FxIMxnw8$i?m6`+^$txQ0Sx)CQdu3!s?i?H{jiCPxJBSZ6&Y*=8(E!@b;GnEpl` zXC+T?`xWCc&^*MTuf;*t*rL2X5_L5)!rdzMC`M_S6@l7AaUARr@uHo8_L?tA5&DNuo}K z2`&4Rg6_bZ@XAwY_Pv^>-_zQhNTG|rJ0ia*>2M$6R6iY-(`L7RP_w&S+>JiQ5tAba zqXJsX#Vz(uOG-%j6%bjr#cCCTuZXBXc5n30dacYx4F7hWS{06%mDp{5>JKi9R1j-= zW;bWEbY~AxL{t*b*PClAv=gEUQ8VpQUBkrbc*REqd*ie^7;#XwqLYzl zxV%JWOB+aB3-(rzuU00&asKi2&z=5r23C|$hrRr$k*Rj{q;p(E*46IcxG*K-Uu}7z zLEU}RQ{=v%CMI$s!BS-v@rDwM;`alj>~$Mj@F2(Vz}xsw1NYKiZasKI`=?K&4_E0p9iATe2>dl* z;LCvJ{__jhL4Q)q(&%3IZZ=`G3TnW@m3xQV%`$1O1-I?Ei@v0l(?wVmXNF+?YhdYkdmp;$cX8fG4K;L(M zw)4!moetPj+Dva24}FyPCL=u>N)r zf_cxI4xF5l8hG4~Z8;E+z`W_cAo=DK-)Xm_cEax!bG%k4fZ}We3%$1^oUm`Yl-hNr z?}xk@e&y+YI^>G?>d2mO)YKlNiQqi|7@e*lvV9wz?~sdj*;0AMb~a5~Eb)_?aH$uG zOWLVR>wMptA${WtR*E^pK30P7X=e$C1^pg3;mt$eo3eIPuBfw3=4@=& zQG&I3S92uhCZgYP3-h1(ZXmu9dXqK0bzaUzqDjXqYc`Pl7NGw6(KKg*p`a>s8R#vB zSHHOR;CwrR^3KCZR6il91f6p2?}JHuB;*SZr_;GHAoiE2OlA^iMgksm?2;>Wkq%Pt z2ZqYVOFdJE_Kqsi)%(>!q9lS-Jr^_X4Vva8nF*q zbqjjOhz~#afZBrdA}hG(bR<>n{N#2v=q2H%GV!9g5s<5+xyt1Mz(HdEm0o<( z^W{TD0j8_Rr!Oudk+HiF9ZBvV<;ad=?TFGdu6IbhdxG*IwlWjB_P5%kaoq*$F-hD1 z9NgdeEG%7a+=(gp;fqW{T0nIGhWIe#X?dd=1~#|+H1?}(F*xW`&LAe`#r_)6ZjlRv)UO#+^m{To>iVB3Sxdg zdR`TuD`x8&%HYv`F@_O(c>nsYs;DxkRV_^2pq!A(40*Wm+jhwJX2)jIu zg$lOYSB#tRXscNEA{K%ysjp_}GGZIhm7Fbok zXIpD1zj=FZvZpnv#FV@wdQ;aS7jkw{7>XP$T(`Ifw}S{$_!QC=)mMYIET-FA#u&{3 z+^~P2b&yht)y_raMWhxeeyP@)su72MqX(*TTaeEm1c2s>wh>Y;jPs~`DbYmIPo z)4r{24S03$*+~Bn4_TX=W64i|_nRP2h~>>{f-K!PBf$%ntaY*5U{_&SKuv|yM(_QH zfAG}PmMvRf&`#r!SiQk&Y1bB@A;{K1-Hn92KNIqjwCs1BI~TI|b%`fmUF_<+WZ$~@ zooHhI@k+^TuBq=hkIwhTjdiq^}+}D?wBn#rG%!s7b$(GTneZcN%V}$oma7$?S}pr1QK&GI<4Lt^BKA(Eyz_rMvX6 z*A)&;CHUdrE^n&kecii#cC>c@p9tQ+*#&1eA7;l~<@BEnthjp5m=y#oXR2N7V9(mx zI%gK_!J#x{fYwL-C2gEbMv6zM`qy0$N*)!Zrsbjis~i?sD2nahOD4_)@dBZ(wN(;} zI&E1dDxX?PlCCmHwERhqMBKa~n7rCr5Y^xSi>6t=o>HBL+%k9s2w^8b!l>egky`mmcNv%k-=m2XJ!45=V$0e%QA!vwaVnsMs+9-Dr! zAcw*Tg&fyR)6dHp+v(A}F;R6C>S5i84hT!7HipY{Z-n-K)fRW*;Eu1#4gm2=(-C$! zHEF4pvy%sv7eE^y9rAAJgz39VuNbFq4(Z$J6Aw(uBR%i5LsAZLtLd$#z8SYkJM-D! z!H*Ah*9w|XwIa@EN~y!EHr1pdLc^Hj8lD(I;>p-l6s*zDM)p9`gJ1_@c}n?%hOU#V;FBi;2YFfHV61vfv)u%gbiW^c zrM?YowD>FmsrVC87I2@C{~J75NXP{T^nPS&Ie>2F_GU3tl8|4hk#Y9%(}V)?aqd_*gYf~ynABH`zu9`%SLp^ZF8h>8G=_~6;) zJ(1eesI0Az)?&N7Pr2BRW7zowt!KBKyo-R)k6=el>|`JAKu_tF2;+HjexCS9&P5xv z2ZoChH4rvrDzdAs)lyy2JecHJ$izt8sQU*5_nh+GHRW;-%5|9f%Fj zIiP)gEOxvVxZb1be`hDsKt$@#_OlyCnQWs&3n@bLcXe~VT=~0Kn8vi~!boimw+2R>XuvNyf@-C=WHzyQgK^lMt-eu1h_h{Ad zE;%pZ=?k7OCI6Q6-UFDW6Q`zYjd!lcv#sL7@&NLKvymbl<)8BzMB4@F4}w>14z(`Z zcP`&SocVfgop0OJNB0Xfpmw0PL zg-jE7@>1*E7f!z4+86nUyl(^G5}rZDIH~;_G#I&;ZmGWi5g_N~Kfcuu9Ur9Qa`+sH zqJ2f{$0(lZX0l|u;wpaZmY-Q|NBWyJqrJ^pxk|<`b7$vIa+~ay;y*k!PA78UoWSK+l7G|p-!ac#6lDC ze?{I^bP+F>7@1!B)M7|FvKr64Q8J!%JLKin0N#3<#eK{*XnE&rHsc${no6_Uf{mtN zr91Na;{Um+*bKI4tbi&(p9n6=F^(}AX=#Hv@4~*w^eT`(IzL8%cnqNU2SXJPXGJ`< zny8h@Ca2vb{CP`><9p<$Hu*b+gnM&oB&?^8ISL0)SYfJ?!LcX(z&%)M0QYNl{lA6$ zx1{K*QfS7)@8;tlFGgKgd`g-qQ=!(yo^4*Z&&4ini}*{%L9H_5SnVQD4RlTk#%l79 zmZJsrCWwA$IX+`7y05rmY2OHP)#=VfRx^z4KN?JVc;l0~g9)PeKrigG5@0={ah76* zEKV${q?GONBuA>v$V!#&8;UhaZQsszNCikY(yHr{Hr~qfbYuldNlAFUqNkpKi9#~Q zeOn51*)!Ge6#~04C>YqeztCUyxjRsvxgiV$#qnq&QVW*!P;cGu^mLqOu@o#*6xkQ5 z5k8bSjlcS_=#rb0H?Ol9PgRdEmb)Z&{)x5*d{w9Ebb#&!_J#0810P!S?`s|e-Ot25 z!=LgTUv>k+m!7$gBfPyPt&S}XW8hZz-eOnZa7(a?65n>6`}VR23X6K56tSbAVKN)K z{rTLP%kqe|cNOa9{tJ{2xJ|=WkNSFigg=!lPd&^&V5kA!EjEuiG^h?G(T?}9Q31<4 zc!L*V2jf`Ju*D$!R@`W9KGI%b0jK1(J4DVdOF~1ygKZG^CYeg?TCt{LU&|<_ zhX0bzWX@6GAr;i#d3l)_Sx0$7(5qQl^o#RK_43B_r5@$! zhNgJQ!TBWEVk^9Ph|l|O=^7fEarmT|4M;JyjuXqIu1 zmPtByB6nStYX1K0JMJxFcnPp#9HxNCrFvx4Y8U0kCgRJ`vzf-mCbjzFG9z>qB?p{G zJBSfo=jW}%Fh?nBsCzp~M+r81;}LCq?~O>{$zj`{x)Yn%(8@y9!a zyd;pRsgavB0GR%lBxeSv`Huqd;`D)t2}kTfg)$gU7CNP9)fNdj_KLelr ze=&x6^L8Dh{_im~>6k!yCFPfnx8hB?)+LB706yKK8Yidno`8GFFVPL5(pmkCbq6lK z-O3;RuY``2Ck(a*9MqSN{ zE8v^B7xQRGbSOzx_+FuriF|>-{bDxt@5Quu@AIGI)!z^EU~2h?rzEyj42xRxY0(cq`7OG1kto+{YC&(e>tL%! zRD5gHK}n;s6k{ME)9iTJ%#j>Wz^t5GKWvw_yAuCIIf{Xu;njj}h)G6v(`KW#^@fFD zovTq+`fGm^qV_47t8mKRE$p!vwkeF3bQ1B4H}%=^kCXk_;Z-;eY^P5q>XRQhHigdw zsybVLw)0-!G#D2S-W{F{BpEY<*5fJ?Y|YZ`$ti4=jXqAN-&J0=+T8mjS?o(tU>Bg) z%sR7ZNuptZxmYdOA?n8(_FDdXNN{ z0n48LFWfpjof{b|k3|5!keu^Gb>)~;neXwEOU(U{(~YP z>b39JhEL;}=+|dne)`+Tit891Ot|h*W$-Jq{6#Zx*s&kj%lh@4 z!K&`6a6g|A_czrvC5Z8_v`FFL+iF^l!yLCOl~-Lf-tdY1C=n_4b1b#Bjss8M?^V8R z)MJp)B0Cn^yr#9lm>aHeJle=DdHgF1M6CX635azcrWravY?uf9*Ek=(v9gxEa1v`^ z>(jC&F`S?r>BjiN2_w+{7%$W?Y=7>D{uI`Cr47bsPpCUf-4Iih49M7XOau@sq z!iN`*bQT(5rx!%@mxwG7nE%EhCVw*a2j|tlXK_SOKF%eD_%{E?;6FJwP?S1cYD^9>T<-fwbb!;LM8+@7ll4t@jYBAUh+TDqxOkqN*ymrJv;t+YMAuEWeT#+ zApE5<^LjICC13+1SMI0RZEADUys|lUb4J1IU(98Kz6hk(!x->GMA^ALwY7*aomkXk zhhjlL`89}FzY=8_;Wq;6mP3d%kPrs^y299Qz7tSuLfD)2NRLh1JbcQNZ%3v#_Xi_z zxwh5eKk`l!8Vyv1rm=;!y9@#(B*VF;=o1`9Gid|QHPtI$YsJ7p`sDq{!WXNZ@Y#}RZlFM-tJY1LR7!e z6@sQa*b<&*|CBEc>aPE%*3{m264>!icE4%n*$%()h!?6Wte2ydS^u8j{wS_1?$_6G zx2E@CgdyFtvYFItIsMNOoJxoz{roz-`;f)v8;E6zwFdp4jkl1OQ0WV3e{CAkUW9ij*>cnZ_=f_I{*pRRdsWt#FwmNb}J>%5& z*bSG!hf2IBA6~ny6F3>@3J4v5UjRV)yrfYZdUcDgnUIk8`@_V$cXL&5H_E4- z+U9abG6szNup)fUx%p`<7dED}N(kbLQj^Xo()wX?NNMV{-18Pns>P0IWXCK#wg25H z;=+t4|8C2AT(fvUO7ck5504oyF=cXSYSK6W;%W*yDyzFa$zKXw+L>6G(JPI*bmacm zz!keGT_G>23dR__ir>z&n)OpS;!^(qEC4wu^0P}cX(+y(2tQm-Ul-A?^Io2M)#qr2 z783goMy5=>RBxtE`J|$Eiln*wZxT=BGF~zl9Ogg3){NJ!E(F~kOnGru42!t3!HRM{ z!@jw$&lApJzINru-Jo1?cQMZLiPw>05QftL04u?40-XDH=}(JW|J8VU0_$4xRN$6D zQ6nqb$7FPhk9cGo?)K&Gdk#>mm5)vVqF$+h&0c325h<>UJ5z8}MJ{u6jdG6zq`lut zi!{j$C>En5wpAqEc;ghZk{dDoNsfOBq%Z^8A!%~P=kfRp9%MURYs^uS_KvflQ|4wh z(v{B_t^IDzj>P2D-;Nlq053ZehjVP@)b6B(aB6^e8ua#f#Mw2R&{iKeDxI z;Zz4wY3%R-+tj*LoclBZw%HHgq;VUZRA-JckXZj6@epP0|6Xnd?YT4EpH!6Bdf!hd zI%lA>KlR>tt!TGT`&Zd`_6Q=?rq#*5I{yP?K0VHJ;spezz$`@)zLl%q$6dzH`8|X- z`H0HvSyj4=zmDua-WpOv<9e@>m%OWRx0N<%{nIy+Mn&@K>a)$d=1rCRyXV40l=U+7 zWdruhy2j42*^VjgA%8p^oDwxraZc{PKGg; zgk;#Q9b#=*2AG&=YooY&x3ft(tn;2uulFFmI->AQHycm$pY?)6j=VqX2o5m>RhO^w zPn8h%u%ZP8MtP@0PANg6*c&)dOY&DmYt4^K0JWkmW06Bb-T}uv1_A)~4xg#wbDY(n zFAdj{ZSAL5>^3VRMSfKP&VkkM#Lk{XU%2fc0gOGb+05p3v5LvNw zXTq(C#S&8UYe`((GQD+VLT$cE)JP^orokb(6kk#N_q>*^^*3}&AFP_D^E(^sZ8!aB z$8Dq*`2^w4ot` zYTh5*(+;$kk%f5M2wvd?PM?oqONkm)Kq-8(uG1k{4&v_3R2fUO45sz+kbh{Q*l;l%Qx?Bq9rlSST2C+Z9Z12;D*`px65 za}VQO?QZ8scCDRCNMEhmF3q_LGWgZ!MO$G%+2GC;I^I8NXk|K3JO{NeM|Nw%yJ=es zyH&%dS$S#X7^ACO(NEuxw!6WHL`7}y44a({N7d@?))qu&spBZ~wobhB_HUV!Lrb%7 zQhF6bM%>o6H6}VB;x*+Gtx|NYUNcO7^TAN?GkVW^VoqXgq4!51lA3#FNsuM zHt%L3kJZIDjh9r$q$gJc!P3T0)j~L)j6BQ=>75n__kUwMcpWyE)0sFVIG^e2l`{4# zKbhCrjI3p=9490zu=>yLFZuq-<8XT+$dsd?qrBpfH<|Wt(tnU{Z@$hjbpg(=W3vLL zC}BC3nfX!FP0uN?pzzR*@C|5GHS=Ctd9Ervgd-dL;)llozuw#*-|_u@&{p# z(8u-^Oo>;YcKyd71f8JRE9eJCos?<@Tx?BkI?qIIiTO8clFiARPqdbU6K&iY+~$CA zgjS#hK++Z5TX2B8()-du---&>_$54h?LP{7h$|C*FT z+@y5E-W3qB>SE3~&_N*7dP^i61PQEe36BmI_jgkZaq;idPj;V@reZ@*NqL!xEDl2` zVQJ}HX-oW8Wzx})vT5TcjNM&{ON+s7rIV#BA0LbR4D>H#z1igCsk_Q~k~g?=juo3f zI5e12d)_Rq>@!fuugp6cpPo8Bwz-kGsgl)lt@l_b*d!>h!e7w+>AmzAc|!GpDe4V7 zJ5AVVXphIo+i1(D?gTIW|`vgo8Zz@_H(wK?nIHxWHCbhuUal1>D^3R`l%eA>x-kYw|e3g}kIdFt#;&v8|O!F~_;~*nR5VkhK;y?k>7mV9~q2YJu=sm>A z1OmH=r}B@fnB}~5(qWWO`oT+Ae(zYdN*x4GT?k;;Las~I70>TDqO~Ed>i*me-Sw2V z=QpDUz7U+OQ%$4F<8SD?E@F+8$;J-K!$-Zzb3d#Tqtx38LVFeB+|&K_05eBut7|*t zsA*%v75k^Dyp;rhPk%Kw-7CP4#6Y_}@azRT$pwBnw{q5V=cmK|*t%b5NAZNY+Hwc@%dP681c`MzKd4ON|2J1)Xqi12mx3-Fo zr@6s3u(k30=2;4S&ubx|eOu*!@xT#|(=?%XQg6wpm!;WWJq^6|o|=-FbOm1lv<%8) zys2;>nHB_tBdjNdU0#Ol=;1)!V$0hDGIz|DH3$03H9N)kIuG)D5yYjDtzR<6@_crH z!jUlI2!MCTdsaH6$8nIVz5HV-s^PsS?!B*EWGy&&*EZ>)eEtszs1=PHIRaG+)v1BO z6>Lm7oK-M4>Xc7(teq{jk?~M>5ckkF9m|Sgdn}lQ4t5I*&FbGTge7W7o%A*Jii%9M z8al7vR1RDGl0V{HwB2zwR_)cC>*e-n%r)=*knELY{G z;p}6UtaDtZDvB7v|Nac22I6MKxIONRhhFKv@+v%VS+`I0Z0Y)fk4jpv4r|Wygh9=s zwBKedvI6YM{b#PTpR+sEc@Djhf=0WP;+$JleK2=oUR%XT4;38_s**PcBhY#^e%Ic* zfopg+IeT#4DkNAssZxO;99v7->=-YQ_NbSVe==4M#JEPkugPlzA`YM{q&CJGgjG3h`rN#K_!XfL7|=v#@0IG1>Dcw zNALDB?QY`FE3^+I-!kq@+DjXohx={efQAXaZm|ZsVBTDDNT6DiCYMC&z%H~~d4-cK zxA!Kc#v-#?QkoxvDIoY{OMrZPG@!b|#7XFs>mSPUQmC;`o=CD!6c%nK{xzbn7ZNz4 z@@PkH_$^Er>9us0F<68To0SNywud*FXfx@$af3EpfyJVFp?s_3C<;x{`o?lce6 z@0;zIO2Mn2AO>zTwPrR+QhdmU4ub|DZ`UYxF`#U77R|mv^Iz)fO}U`K8f^}O8X4hN zz4ylQ{eB}u)Gn2acB0bIkt!I$D{{sPOkQTo9c&PLbdSjQsXeKiFFAo9=k~UA-E`_N zGxumDD%ZOH8&y>(i-CaDrm~Q-eb&blTgf|%q^{m1u_(+5=DP=^To1UWQ=1jzZ69F9 zRR^~Z{0?59(`zN4`zS3{zHdW~ZW#0KjZi;6`*EwcdcW&rU~bcar-5ZP;&7Dx=~2q@ zK%&~^v&W4G&wDQr&N&twr8ko^<3n7U(0zjrbIgeUOZQt>kN*6xCeJ_{Fx6WQ-=~Hz z)8@v)aI`NnNA)BD{Uw3&mt)mZKmO;@MovB21-7j>&#QWiP+UGI3X$Z+ljo1hw2&3* z+4kupFo&a*olEWm`~9{y)Xc?x`^@a_prgKx8irMUUKqSjp-X06a-|-_zuu>p=4l1x zrRVV3kN}AlsCr0n0s1TLVc%-SYyhmDHupQrrOlstaICOAOnWj|8d|>>OExgW7dZ06 z^$+-Qt~WMA`*{uE%Fws3v=>Q9g3&hEBxrWqBG)M*M|Jc>~# zp6n5bZu(b(q}IXB-!^=W;Q7j5jGEkDyw&H4+R|J4qR*OerQbC8zQw3Ve>9)4l@XX% zMi?nL)pB;ktqM2VF}il4^HcO)tqy3_u1BvC1Ayix#{@f@Q!bl@T<$9$f86_y z^>7`obUZgTvbnXJ71{LTf3u&o=6WO}KTbfw~3SL%ncM_<8?j`U{yEln(Gg%o>Q zZy{$G>mDu*3*>L_lLpO>KafuADZRsNl73I>=NLQE&lgVvb>47&KW+aShqx`&*?75H z9Zbb`eYaRSN5u!@35R|*!H_cY81O~_3y3b^+tXaFKVQ80dH;R_x4fw%??9G-l{oAP z#-`myTyg`}hn*ZqniGy0J8yHk)6@0o%#@=!(o^zrQ zrA5}DD244GQyZBhbqZ?fo8*JS2J^R|Y1gf6!A1Aj*L-&69U~4>{z1EAFe_djtIgUx z{6O@TPMW(D<@6;ZwHEz?@1C!0pGm&mm-qEt^EEwr>kBZ6n}N%I_iz{P)Xy_a@~RKt zLdrHa1vFn+P~TMrEt?4vO`U_Np#vRq!+52z=SjnJOrMB;os7oT(}T`es6d>S%0s|ETn}VWc;mz(J?4Shb;#77`Eg#K5domrXny7 zN|wAA)O@TbJ}LD^-CpXK$FRmKun6KIn`vPSkRc< z2U?BDM|xM<^cCpPFo1!3VJXCd=_~9cR6FqvV={+sRsE~pSVkFIO}29d9}9mK{Svsh zu^!8)pxxQBjzzOU_IV>vC-T|_hj;zc7#x7W^GiupEmEx?!cv(NwJSuCXb9dw z=5vYX8E&ojuxEDWC*5Y(P4@=*XlnF;ksEpSf%?rpIZJm*+z$d5s{4N<=YGjR;Bol*Nf>IFB)S-K11E6vp77E_k zh_&eo$)jBFhM5b0cA1?95EmW5rF#4I>I<1cby~ww148RM&qvntmr|1I5mi=7ju;|B zM?>mIK=fBb_M})Znl^hW_apM-jq_hB43Yo!SfDWcJ9m#9ZvVM-=7zv}t@VoOkBv+7 zHI!uWA&PgkQYl)>uOQjPiM0v1Iq)f7bf^Kw5gQ)Nzmh*}B_D>OTf%N2_3vzxv zC_Q$w}K2!qtyKf>`1QkwEmzVGyH>4gI6v)U;C#vo1=re zKV;uYq&0!50dGqkQ}IY1S#@~DakAcYvG@atuph-&1%y;UcOj%rRLEj6hkiDkrvaqs zhb+6hwfwKhKCfl~i>&Oara_+)w{CdvV zdRg$FV6)q0uR7t~7?%&Wy-5m&6Omrk)gnOmZata)iJqkT1|XKQ{_Ot@@>Zn2UvqP9 z=nYdHjTI1#Jt(V8XD(79zoswVu4jVNYTuBgTt$0A{B29X@}o6WD_6em zkvQIpzRn?W;qRY)+axvG4ysapqs}?`vFBfQp}S|H9Dlz@+}F4HqB51vhx9|*pUyPG z1Eul^F;Z+Ue}*Q5BBWqNyeY(e^kSniN=)DX}Z6A%l_eQ{VZa+2Z z(;4@r+ad?5(uUc(@_chGQJz(7&)mVu7w)lM!Uc=5xvy59DYGZRBxtRFdX??q%Rc)L z=B{G=;FJRX|78$TW1OZ@`Ywm_pW_IcvO4s&Bkj1Fz6+{#N}=VqD)#s@!-cQA4>S)f46! z{n^cU=Dx1FK|#9e@E%ZEEK!|Li>LQ6H~l@HK62CBe42-O`&+?A!v3uKzJyenkumvl zr`de16EOLx$sMYbwstlwjeNRHLh06iS~Pj>X7JhSnY6cX zR{yHDkDCtLh9^GoFHZ-)7^)b`C-TIORgP8c=w>Ztb@x<7SJ&~9d&4CHIuf+Nj6OV+DAkPe04A3gp?&?J(tWz0!_&Aoun}~Audmu>&@X&amKruk zHUqu9R1s#u;j*rQ)na&?Lb~!M-dngMHkmA7x^MVuz2w)GV!{b}*W9Q_aaAkWDk`Sk zzx%D77QdZ=o8i0^%{jFJy^-;qGTGmzQ_uxta}7^{ZGtE0Er(r8V>xK8g+@=`9YhpS zl&ty@3>tggurR9*KdX2_xSF!^*md!n)P80Lw~$i)xd_6ysgM!h)7`nPmVXH5o6DbB z5AE1K7nM7Ydr%7Zd#hS!B8&}vc-vR7(3!UdyT~{et>Z26&mA{E=(`c_=eqamnSNve zX-Iz_$6o!<<1gr+3%fHjC)Y#GWN^RrZO_^*R9+n>Bp$3KI6At=ux4tRse~fN_YM)!J(6+Z*Tnz55 zqpA8o?lr?tN$>uX9YQ^86#M6-25X~dbvZM8uEF2tXybTiygh)K^cTb*xpD}!(6x#Q z@4?pY39m}f7Vc+MsfW-n&HB2|Ksg37s3-Nc(MI$np#(+0p@+xxSr?($*Zj$x=v^)P z*Cnn+9u=6d{#sYGJwV^IQ}L|>)Z&qjUnNhES^Otm@;@bheEDS;Q@P7T|BZuXy+|L) zTe1n(5RyB4--Kq{&;CBaCcSkc=()Hiy%((x($wAPxp;se5rj#F|6Jxkob2b~W#=5; zgO<-mekGW$MeC1{q3kxiNw!#=PVRJ#ES=5uLtRVPEv0QGnI9h0qH%J9I5)gtyY_R4 zmgS&1X$AxTn(w!cjBH;in)#n!J~{Qv#vwkH6UV}U+j$NXiZZ@8%!1komi8Mu!IrQ& zDzN|I`&5taivTCr_BcpI+bc0A-wv2?lM3y6gW2x&bi$NGM4xDH;iX2m=ThN5g4K!q z!2}RP-+S0>vW7SG#IzMdZ`RLR)0|i{(WhKa(YQq-oR_7Uz z8ZlfGxp6?xqE^FtBR_3KXxk=09Z3?73#S@W!rDd^w=ZZ5-bVRb+m9K3;52N7Xk04& z5H)swQ2K?B#1NQ0KTSvBoCDK+1EI<@#BmMvqWXPXVilqqGPV^RQ&-4#7jV7Y#hMf& zEiU2Gpy8Ixe5(Wu>Rj7+95mKdcQ_&_qpMJ9I_1cor#Wrg3J*RkjeN#78W04I*2HmQ zs~@7PM)W{KNq?=|pEh;dPadVL%IbI=ojYhux`o0H{S7(>G03oml=qKP6-zvD9`h9P z*3^w|zJvyy}ed=nAJwlBpBPkO*xF?JtGu%eNyzw=2ap(|Xp6 z-j*5zWdB@ZAX?`DB8xgR9#^K`R(byEd^<5czP9m~m<7CU^GjqJjsk{63q*cCXPs$6 zHIzrEv5mV!)>O0)X~n~gO=BuxDvGg z!Z@dCrE(u-8(-P3PK_`V1xn0N)PpgiUJj=5*y=9@I<0Ex&RTaH8c@W6t?E?hIY=Dg zC60Hrk(y{WowHuOy0F6XM)Zvp^3BU`@rQab%Qy@1DxEW2|N5%=9dgyE26e$j;wC`mxjj)?6h+QX zk8v*xgr>;Ow%`KI5U4eDMl7|mOK*R7lU`VxW6T#9 z#7hu{A#VY{6{OGFwMw3@ff|3}YwOcQ${1{3T<=7NTeqz?WYPSJ$V)1tjlO)k8LRgLq_t<)53<1CDRYdC@2>=m#=l-$FR z)Fr9gWt}+XBbu@@RRT|HHii~&Ar_l97&=dj56O8|f5p`hBU^#L@B1DmRk}yIE$G1E!h&P~wEHIT) zBMyoG<37tGQ}z71_Qj4x{B^e;7c}T@H*Y+$C9Zu7931B9Eg<5zl z8n*Kr-|o}I$S|1YfmUOWZ(C|cGR({ZNrkWTKhhS?YDMzgHHrNew8arovQ$H7Isw36`g4Rn)(e$GD;qm?%ID50TJ% z(2H(X9be;qx4MmGh%cXtLX&@3DV9~sInSsV5Q^=ggDAbz;&e&f!ydO_YnukeG4X;R z)o=GhdS%cUtF|~oD^kbHt%3|a_7@hY4nTM`6&?;ir_#|hNYAqjI&LzZ2iCSxP>Z6I z_7bKZ@*(Y=5V4n3tc-Zrou);GKA~cW*Cyl=O(oBY<1bl{+MS56|9=*sUNj3BIXO_C zXf!zm0k{EH=y^EsNog!(}CahfmlBF92ne&Bl$Tz zb7U}+<->3OB$eNqyMyOcz{7{;y-4W~c9J$4hB^w#{_uf;*9vFm+M1ygcB}p?-($vK zh=Qp@HZMgRG_j1);S=U$qtgE&n;t4Z7?j@|CabC-%z~)3llns( zDzwm^oaeDh5!v}689u$|uW>;dY6Z84hmc!w zC~ML?kI%8H%lC$|)CgrmlTga!z&9PaSZSw|4~>j*IwFVGHc% z>_c`G$@X>lt^Al_Fa*mli3lNVex7VQtRLbH!Zjn0N5B^6fAfMGhSkrMxMurieY*B` zV>QBLXCYLSS$}3N`$|c{Sy_Q?#0n@PfF0VULA540(YC7#o10Yk57~0mz|zwS#7u6e zEGoX`?fUC@tDx%e7j3MD;@sQ6;_c{VA_uN*S@@!FZ#5T;o9{_#*SSj{qO0Dv2t?pk z#^X?F>Cnw~O70LStS4(IEZ+K@qH*+LrC71ex0ysGYLBfKfLoL@hLHX}Ry(5eQ2nHB z5GJMr;KcRe>|PMNuD}~kqZH}k?cHiNx8H^EV!x-o0U91Brsz-qzKv@7%F^Q&RQrQu zw+F082$i1iT}vgOgVfA|ZcM~q^2ABt`eqS714P?kHkg-B4m7rkrk|uItfkK5NCqmcjt&57YK=Z~^S0kVPik>eE{#y#4C7u`)kKgN&U)JHAO)~#6l=X5M3aGoDRl*7nU%3YUcZa+Yaj!|j zp#}u#%j4f1XTK;Dn4%Rkc zZZ!)%f$}_ey*ikKuHF^rF4g4un)Cj@Rd%fUdEnRegk=Cbdi65?m|pZ+*x~)5kv0>h zC=aQNpLSiF4haI@TeZZXAm03;!`wMn5tm7NSm6^&WBdNlkc1s&lki?EhCw>)d{qO{VBo5LUEQR%3Sk}aFQCx1PtloP`QIhzQn zzh>z6VcNbXacz*k%T6-5gDy)ENhB|S?K_qj<(6sR6^(ERIUc&m+n?^wT08%+W6?HG zrw&zg>R#9xiXh_qt+!WQZ-Lv+sSoi(<+km#a>lVd0y4M%ae#m_JFlo}a(PrfRHZTR zlITHB@=~d12{9k{Sw$tJzP4FyyVm@;7dp;|vB>d+>s1^ny;u~8ls%Dn& z&^w-G6l1%wRC*)K0LRgtLEy>J3OPzM7ZE$1u?cNdU4PGz;Sp1XKd*3=q~6krbB=O8l4m6sP9gh6VDm-V1Q2pBb4WUd zBX}LK28RAs!8ugP!&AyO*`XMiNa(ouLUHwAz1~ zVmhVota&+(8NrtTIaDTJO2k^%p5WF{){ z?e1@P&PrM7+RmZ&XBD7(d^I}8x;=3{?-yYl>|t{xtawaDF|dB zv9G3^NUhb^JEPAjUT7RD@lg)6OupBV`vNhQ5uemx*x0jKto}sbLMqVU)V%B1a>Az za~+ZrQ!k}0OnNQ>j|~?@3!mX3Exkt z;y>@#Z6s;zIzdPXMO&kyQPTKnE=bY!VDSd1hF!vA3q$Gl`3^Lm7PD{w7iqsh2( zCEg%Yjkym*5i{=Dt~tag4<4E=vzT?2S|(h*Kbu>l<8TW#Bh2(exh#2uR@kk21nzBe zAdgK9Sk)v*{rc=q{|oOrkVnw%mxGoH^Td1E!s$tN1^l(UQ677bE6yFUwXTk8Qbzv@ zzM(lGEJdm?RM?NRIFEb;sm0i6V^^)dzWSSyr|&s8cviX2q1oALII``1w%3SBoM9U8 z9R0-K~K4^8@MeY-SAtyU*xVYMs1MLcqT)+ek! z*b{kvamxdZH`#*E?>tn;MTMkm-C}KXukBI_mhPs&lcjS(P5s@zR77C}`|hNhMWoG*XRe^cSatH(Er+v^x{UTgI<_^tw5HCT&~X9o=X<4Kg|Dec`!$ z4*$CMmm$PQWwYY6o;ptugdk`|xst<20Vp+U?Kz_?Y@CO`0^RE%EK$cz=zPadcdXhE ztgu$25R=xvxt&M3rbH$3RWukBwvZ5rv%#?8AncqKWfZ_8GU~7-~6cf%>%!w*MIh(Bc47HR7eGNxD5%+zv zJEbHGgMVVRVxkLvP<{;5 zi0N=6to4AFp~6~P^)<;IXC7R~qad1@s_KT&a%>lDIEn1$!SJ_n)zVwD^YnNk^!Y*;?HmcfKdwC0VLTru4sH#*7Xn3Dv_{4`M8K%|bp; zUPX72RunpDue@iLq5n2a)WoQeS8|_#6 zv!*eTMl_SB@P%Y8)u~bu;pwYGJDX@__!8$LWyzyWI#?|^TSy)%fW-4FF1mSV8=#V( zcpcd?tpy{E&OO-H(32-EVaJ;{DQ1Q z=!J^R7$_ZMdWu2qtMwjg@tO9&3GGMi7uw>Z&XFb8vy9ti;Wp%55PJbW$*GML^m zy*&VN=zS_9V_8;!G!HA8a{UR zcEyG-MltqsYn~~XO`Jdv$B5yNvYNsGlj-iC>Zs!!Nl!1!)ohsedf^$^$s?#vh-1Ap-E0_vWT9SlfjA4?6r2n z!};9XYLI`7vdSpT9zQmCiUM)!DP%2(+61pV!c=R3IZ!;{+Js6DpeulW=_3Z7d9LP( zYO&3G{ZqYAk{)&9EqAR+gA9d@XGNPD%C(IA*^~R~tfT(Se!Co{rYy(J#!tYhQiu z^=tb1QEhWsz2}n7fU$i+k8M4`SX)0M4#-!YvT`ijif=`TmpVzeSxxHliI(3m#P2=C z|G|agaNt@2PF*P9(95o3hc!-8D?z{tPfWDxLYP5K>W1#T*2ZAot`VFw8$ z)S-U{^QT0b6!>G>W6pY})}_qua&S)Q&ZBZ5wJ!0TB`;!@kY=blpy${TdM> zJF}!i*1(mZ+B1n3w?`r6aN4HSVZQPYmEcs#vQR@`4(*8Y1C9x0Mva#T)u5#iQbOH0; zd^#qS$Qmi-i@}*x_bpEfaJ8F9w^Cz=*x;7z1km+I3_o8PaBFi*Wq~1cC6=+znRs{( z6$?uDXyOyD2)2$jBRP{rUPoU!9P8W5uThA7q{82A#OvSv)jWE>t^$Q@`k7oWW1BHt z6jjCq6mF2sh3iQw`^M~mxmaNUd;7g#$3)BdWLGOt6H&T=*WTHy-H|T{s z2A(bGmB41t|5bX|uS}%$;qg9Qi&=lG+P7Qat&KClnsvo}X)3ttj}=uvF4GgmOb|fw z#~;ad_w0oZSl5;dmw{|S<>sHa77x?+(}>RkDI0Z zJOBXeI9cD9oD}!1)AsSV_t!SI&Jir3iW6hH{Bz#EB(V3S_30#GPy%6HK`cj{mmfw` zgB*U=X>@BPEA8)hR>pphE{LaD>O+$&Nm~czbPIfm^`B=Zo7si{I%ci+{RYF~eQV>V zuyOM^sOiNwJEY0W2cufKIcvJYF=E^`y(6ocUT}DWEIxE;J^%wX<94O)(^`V)>{2CS zDlwp}VNKGnFux|4Z^UvXlX_d3sD2YmlpN-t=6b4PK-~w`z{Fm3=u}g&$Aj)VKh#3- zMlx&kWeK0Vy@FLpufo;O5!#M})@s~p?S9Bo#|#V4at_y9 zm>Dx}6$G{neNz5?y~k4|Qo5K!aSVmzET640__h^qFjVpFfsxB(N%s~v?x^_iPgWb! zCIkaC@x})li9R8t(42(Ppf-POBN!ifMomYh>13QPsDqC6i8adiP8QkQ#~&egnF)V} z9&w*2wCT?EK(c zwUpMaa+;u=be7f?5bL{EVL;B&E`_Y@wSXw{4jJOD!4kTTDBYs!N?Qn=ZuBrh-)BQ~ z=6ncxJfhbg94JL;Gwg_83&4h-Hu#y6!FH+COO)sfq{$cfFPEUEliIrEo0tW6j98I> zOC8v5D6eXPhp*Na@QzBV8A3^u(8|2vMcU^(w};+-WYn4UFF*bHGj_*0a-jbD)^@T)akCv4A}TUEr&bm`mxAg1gB^z%L*FfRANFz)+tKrLd72%|> zb{Y7w0{0~6Zk7hogdYL+zx?;Yx+3;Byt@(+l8L;2+_qF5Cv(I&>|SqJe-V@2tWHm^ zrCplaut%~#(1pDL9n808cc9oSb`J4?oj=Q?WfDsp9>XF*WT{F-Q9qb0h3-%^}lfKGg5ciMk90wrm(8DWWVWI(paCRlR%E> zr(kY?2)$(BZ%yGxTC&+pB=Io``*=cU^tP}Eg5T(+$<^zkR<^EVwI?ukq{9_#S=0z6 znAUXV-XVK?&^zK<;eGu*+$#HlG100)zpyjLvEx%iV*@52+BjxN+z@b&R+%tv(nu@L zD3W(8{ID=_rcdL%gn%UB{+zGLPdeT4wn2RS_TeFb($8?=qe6<%7&lZQj0ev82HlFc zFEEGZ>`_mO0ec7Ydrc;WuPQnb?AE(u_D>i;yjG;9Iq2(o{(&kB-OSkgq6MZY__Fg2 z($HcMYorM~C-mQaBMqZ$ z;b`aeM3 zeHSIMCLwk`hfRw1UJj?``?lZqia*MAQxvIMaw7vPMkpqF#3=p-ZZ?FA&!{U9?y{u( z>8j}RGnAKUyC30SJjgst+e_e201DIethOgB3j`XEMgE03+xW$aZE%XW zh6ZEllcZ16G7j$=pFQ-+5c~0|>gKq1QwMEf3Xxj4YkY7^9K{W`Nw=R*zQ0?q9Z&m3 z`%>n*>8GBM#m*^2B5JmMdOdBY%ztS_?YYd> zsY6VmEZ)!CFh!w#pXp1$@bDne~&I& z!!BSiOolxGE#7OXcfVz_{Tpgc4Js#OsO40e>R(MG+O_h24fc22FKfx< zTfFqY?%iX_VEfalf-vOUJqc##;u{P$Z-cjr_%Wl(o5Om`4d?-yp2l_xFCm;C&~7lr znCj_0)V*VBuYJ5p#vfcK93Y@wAKkcmcEo8)jMIEs3uBJM2SatNK@R~rrNN_0u8mgs zlL^85!S`rVPsp?Es_km)PZ&IPc>^+d$_uD1SMu&ucbYxKGdOt$rYKZb&T90v|1>(4T}AgI8c%k3nKgFK zTJ1mz+w%qQG^%cgUH2jr)}i7Tz6M}>WQf?czSp?e*szAmf{~-rsYb@4L`Oj4DjnqU z{wK}3bBa}Lpu$&mpU1J`$m(foqh$GxxzIuWSkqUDlH6e_Fyl;sfdWiUrgch${*iVs zmD2a`h>&)Zfr!)hDTp`we>_mo53`40R-HRu$Me*I6!V7XL0p@CRbsGSAeA2Km^C6^QbEcqrduf?1%^8tW zjh2PfT0X&0;D#Xi%QL*;)8#YTmvs)W`iorlr6An3q6?T9Kia(>iK&;K7!7`Q$-_x# z$&=r1$udk|&9qsxQyWvyQ!=F!?-`f2_+t3>I6gBZsX@-GD2%^K6th-Z z2{&`CcsTm;If<(Q(2G{uE}kmhqdP2@I-D4VpYg}{rD=>ICQ?GS^AQmT<%$mfy|^^e zDr%{9gPzHSzR5Y7hv6ic?x{&u(uTL7WtCZge?c~jzg5*K%V{)3!)v!c2i%h z>uRTh4tHP_H=Wn4k#-aO#QD3;@>67So08Op)8g&Z=U%-Q?>~fRYa%>f#j_E2weCMx zN2=?rK@DgmtUSbsHE-e` zpi+WbXvd31WU<0@w0mPIo3Ra|YD8KueA|wth-c8ON~k4T+5E@+exaRGquMU4Hj9nz z)SB&4HuL9S=#I+G-EDh&JA8#(5g}} zVTGA&jY@S~?baJVRjIuazLlJ6?o9W98%;9K_#N`MM`*q0?#x#BhecK$i_61437#91 zg^NW3pL_*|q}Go16|Q0!L<1R4H&8>%MP<@$Tj8u>UbZGQB>`z)y~$#4TxFvC@m~Ln z@krd!s#>Q%u?LFdBS(ogJeO>iqf2Z^`O5-WXBm6L8~ImxDd=rVe)Z3a`TgOG-HJe5 z2-s`V1{%IWKCi0jqjWlA^+e|aj)>?w(u5^6b@Fckarp4~f_SV?(#{3a8kRG?8`*W$ zSZgLEgViO~R#@_s!;Ya5+K>?sTWl3&vz?x}-k`CWgLW&)3q{u-{Sf~O#OnbAx8J$- zgok!NoRI~-xI@@9!RiKNi)#9KEi+QmW+Xjw8OPQ$f}=FB`I!EVZeVwxve}j*P$JZ~ zsp1AR@uqz7Uh^Vz+Ip04GKm~KC*!}YtO8HV{PnJ3I~Xj3is`KT@AOZu60-wukB9JD zwNGa_(dTihX@B`dvv}_Pt|)xn)JM};AZ<= z7&UrVJ6FG}}wVm0DnN_wt( zBU|Lf>sLz#g9V$6yf%de>oBA&_H>R(@#9chF30bS5|Z{ng-3|nhh^QexW`CxLNxRL zWdQ`93OgV;Onv3+|69%mLLpwaEd3BHM7Q~c{x9L zWnz*fWFQgCS#t5qQtLL!5m;JtNbSp*1+)xooy=d?7|$nGxHb-HKto!-qfBBMpvy(r zV}5`}c-kr43v^B6L^{=LhpMLjHOZJ(DXZA2h#9UThs&?eRaQy9V+^gxM>Jf z^nFoG!_iH<)~jVa-Aw^*sCYXna4mr9&D;A<1*8Sn_^!nf;@-XMj0)W{#27PPGGr&b zz9P>y5i@43#=mR12iOo_yjN}_O)ogQaI9z*n}i<+EQH)u$oaa~a|auFp3v0u`i7T0 z=vd%HlYhy80!iStMVcLbT^rKQ?_xH@j9eC-7tX&~WA=X2i-z^Lk8E62mA)O4)`^mQ z!wW-{<(8t=?KI%{uOl(FJTO%A5;&iaGe5yb=xs2aB*{^3Z6nydW(MVMQyPJZsidha zPvq-$g|;R zgiVBBzUIYSGX$ydZlp>Tce*$9SW~}(1_<7?40C;gi(PQ)w@1$^Ytod&?HxEf-YlD9 zz2lO23yi<{vPS-e8z~-yyk$K4rg7>|C`8+4{Fo@#`m(X~cC*@s(^^fN6O|5sg$F9> zI7{+O`%5%9A3W9iqUKv9{wzfEGe#dCgWk|VW_gtAhxaqiB^Fg~roX+!&J|oEjRC`54&47FtQ#}lEPSKmU?5B{djoaA+~YMN>USgMew*ST zLjqd+_?A9Az~{SKBaxeu1yGtQ z)c*F#g*DZ)D#wvp`Y=2~rm3TviJE4YxRaM2UKY0YNt50HHFS0LF$)BrF`k)rk`@ab zi@Ba+q{o}4E#ZfIx6zZ$hPl@9{8TL>~2x7TzeCgZPEC0&n zKkg6A66W0P;B#@+>1h|vTk{az8*p92AQPXB$Pp-I4sW6Qxo+?x;$SiYg`%2|y_vLz zcuiG?)>Rk04Vh{`J`N;mvzLevKJWg%&8Z~(sb}`tS=Es4&DpdgV9DRlj4*h~OrVL= z7DsZN4^B1nQyc3LTp^A=Tlo84nQ^* zINmOzVQuqP*lqH(st*JT(Vy;ZC{*pBLSNdOEf{8gadR5>EZU<}X1Wy%)}1*{J#oS_K(K33XL>X`HtUMi^nFi3Mjd;qEn3gXPv51UftHhz#h-pC z{db{ut@ri!iEtVWB-JFEBVwpA>^WpJ1nf<2>YEd`28Nv=AZj~V1+7QX2;e!0)ipzk z^ndl528l=Q0`mWYY$=0*6Be#j2Q}idh5CMwQ{YX*R^E0@UuH&Ab<(`48f%t)?zNHE z9~c?EskJVY_C%1%!3FMndCR)JnMH1ZgaJT2zRpUNcw$gaBJwdh)tNQpfhVuL#L2#u z^YKQ;BOfu$))1%n>uH*ZRkO|ya5@zFHy>i6Q%MSKQm7gI6|@*_J=Y zNPLp@!r2b4tPHP|SaLWE2_VJ&{+Rp^gTwZ&OUC0`gF4n&5UCrd&+7U;O(rw5|DLGC zE8wpeI|zIc^bhoGU3s2;0rxbkS#G;<_m5Bw6rRWcXXMYXgegJ~?8>;Wekk|`97srg z!sqi_z{_jS&yk@Q1z|Xw%d0!i1_jj4*EGDx=we&hBaDkKT6!-fs?}U)j}z`WzwrJt zhknv9wkZ(BaI5-$%hYw%{LU1`tM%qe9rHuefHBh0ysl#VgV#mLJwF z{Q_q_7rEZ@h>q@l^^|in^@gVU=wyZ0n!^!FPTaP+n~92=wpJTNB_kzQ?3iul9WTC| zx%^$Zo*>oF2jBekGhC4oNTnAwn8Z2f$$xPVflq#rn{x}b_z~{8WWuTUO~Xy)w3p$k z`tjF+XMv>U)E*b0!LU}^1`mVI*&}~tFV2p#6OI*cI-j}gnex-vN=+YlTacTO-C+ap ziBYoM!N~L)Z0?X3vQ_#fea6nG<0|3PbIIrU`}O*&tWOYJb}QG-OsYaPlHF;UCj!K?3So0i^L(?tNI{UwE%kcnRRXTpZN^$B<0k#_qf8_Vtx zRTUU^+oSWi|MMh8b7ien0$_cR><R$O zvX$`XewWt9pKq!e#?)DqbhfJM>V1on6Vj8J>@kHqOS+N{a;Hde(yns8ZI^r$w;Wy2 z&8~hvql%=P;%zbE^T_cpdpDW8ABqnn3m=dmpVu*5BPXh`DiG~y#~ooELoMWH9vEDb zABy(+4eBSr4FS9fktPRnJuQZ=+M8dwg34K7rb!k4mH&UNn8@L{)`xe=(&Z&@%%}x{ zM+ESmv6{HW!HDvbpF7^h=VYKjwS+fd+<0%fb36hRV0S3YIGI1jZf_o>CI61=ulvm^ zVCKso8~&-m5`WfcSYU{U!ig&$fxwgryCbPW|nq zC&E$pE7G84B?+MgCg&!zNozJ18a$J%DT)4?(0R+wS(m1o-pIB&6SW1W^yTRO?_
    =i29>FIuTsz%&u@tsR^jRs=B5|tuMTs^WxXEeKVmNSWVog|?mehl_`R3T zgp%B`o_7B1Rvb;jU|6BT`Ug+u+K7y}&Vd^jy;=z#u>{RqrtiCHFC{VsoQ!Na5o6Mm zdJ;RQdn;HTqwIjlU~Nv3`de6df#5w)IeuV5?{L+#y7Jl09?L*POPlzjhV#~-8Za`j znwmYRwbc&OmTc%H1;jh2L9-YgtMTqeKDw-aME4jm zn)K{D=!1WF+@Y9yX}3tll_IX$4Yvwyf^6@T9_wE8=lH*6k;QxEWZ@>!XCYzir-#&g z45p-G{!-IBP8}5ZXtP{hVeMA5-Ndye?BcYi6`M)s-wa8${Jz=C(XGyu1j{=P0~~|x z<&n(XWi(7yQ(}>(G<;i-sD940lu!3&q6~jQb7fC1l(NV8z@tUeZUCn_^LwOzY9*2?=e8Go~?SuFuPV6X$ zmMXX7Os;DH;dQGx^yTJ_CDXhl<5`(SXHM76KrEn1goVAYVM~RA(y3 z7oilhy0rUkuBy@u(dWZ7qK4)cmo@hYY8ySH198?gannm`Z6%lWh2I#)h(wrMi5x1R z%SQK@AVBjPBJgVn9lRwcWtev9{YB*&OxVzH&jJ~eyVK8O%49}LP>5c~vOl_vRC1`$ z!N=T(DbbH-1FFQe?#QAM@Z{Lc88@{HU5`IoI(E9@x@~Jw`IJh9z&clbSmjc_bK;5k zB~^}8n*gMEp)t6GreF|NJ3l@nmS(NZ1By;FSg+^>PoTwDzn_$LzitP!!5WH!gl-yQ zvh6?*9$|Mn0JO~&BPQ;6cdtA3OD!=Tu^Vlb8-fV1_IcB4($-5M;6mzw`;UpP=FkJ% z9o_JSK_rvj!^!V%iC==`*jqo-KDJO@Je%UI%dr+s)LzK@#F zsO_4*awsWl7j)H%yeY~rw8$uZ$R>Slp)}9ff#g#m#LU8M`di}GXU2oVa}GO$s{EbW zXtwx!#+P5}sdG!TZhNUB?p1CkM z+Hw35$3IXI}ZRol5VzH(DG#!<^(t~_n`` zmyD*VLt65m4&o0$<3?`>@595VaTR=(VI3AqzQAOlutT*Z(8ihooes1OxBjPS#gCh1 zy>6^tHT6psy;#r}ccRNTBv522JKU_ZH9WHyN*zh4KA4zc!2%8Yy8-g(2N%K3g?~U3 zF>5Hl4%!cK12Q6A&NO$fwtNX@+iH3y0NJ%{n5z9{#^LjLUWdiY3Afs*ncA2G4F9T? z<|%D@>Vgb@fJU&wY85awG8fsYh3s5#Cv?rP%^s3tyQE5QQg~TI{@3qk#?uBFzoTn5 zS1zZ3QzXESj-E|(&%O=#S51BFnOF#7bXB8vJQ9Ww zH)8vv3f<2_+nt)Kjt?Tr3%`W6k5i3_;J$b)ZxZ!0HH0rd@^2h?f-}qHMEy7;9gAo? zb0$0rQo^Car@2CMP!Lu~h*n??6et>87y~K@4tOh)QaTf`FN+ZAQ3|ztZApM@39xmW z+9U=$1xTg#q;3VPr>g7bB2U-|i*3a>i#2Z3vMfOr_}MX<@9*jOrfa% z_S$7dr;ZNO-{q^Zev4APoEn#UQ;hYxpTp|c1e&C04+f~q(-7>B_5bV8>M?iFzzF>l zR*%N4s}?tqS1T9Gm9O=2dWYI>y}ebN6%0~HDUFN!biL{l&S1QSc=K-N+oJkk3L&}s zaT+>HW3~2jLBJ))Ml3IF6v{EM+KwwitgWkT1N*@@v@-OMGcYopU2xMH4D8Sw@ukXI zYm`0znWubbCIqv_7L2m})CBnfAaCw}7~zlLAjUl_uy11BoVOg{AN4U$YY>+RNq>#N z|5H=5`242Tret61x3i|v>3xAmH$|Y`xrDnt%e@q!ujVQ%PbBRK{~nZHfYB3)AEynq z&x%5C`^pP!qWH8?Qr3+Asmx{`N);X$!JwE+Z6#~HFT#+PnDSawi#(Hxc;b`k4%mHC z-cQcc5uArL>w9sMu@^Es<)oGa+fj2mgi>_Ooir0(mGFOA_;JmZwLH!CHPN*pcf=~t zYZT$&8Vz*{EaG(=ax?H;8T=Koe}8piH_AkdqmifH{+NxA`$F~OS@4q=m)O&j8y_p( z6W?o=CZ7LI;il#K0I2=@xc1oo_xu^QmnrSkR(wqxrsmG)g^lml z*lNUm1BvZgF5T`X=~=2iNr{`_6%oaVE+r7uhReN7^DP8mpIho3NUyH^9swE`;)`vE zQTkR*h)LA;0l`@ks@=LFVMiNFjCj9Q9MSoI;(Ag-Z6Lq$nelr6gR8xq4aP^w?rR{o z?NA6Y+}<}PN4(q9OEesgo*RW&w$2yvd*F|H6-BUS7LzDaK;AsON+KGJQ8Wr4OvmQ! zgslY>Px|3S^L;c`wwqKMa`9)YD-inn7;8I&O8yY28Pp2p!r=sI2|_v4!c!|pHTk7> zhc@lG6Q}4qO22l@zfL##FDds99X3sp`OTkW*Vc&L4wV;NK~L!z0@_wMD|l_DYL(FEE|i=VsJ|+S z;*{j{Wir0vqNkW!VbfwGVM2A(3A3+s5&Szj}em*|v9&xnH8Pp0+w?svcNu z9S;5Ye`u|~p_*}_ra|g*5Fq&|P_ZYQalt+_@~=1D1EBE>)JJFzwE?Gl-v~gAI?Qc9 z_n;yTMpKzW!0@D>-2UIyUxAY2=h(<4G{4g7G~g}HH0d|l%q6L55K8Z$%OwNR1-VBI`*~3ixGcj~O&a6IVRr0MYPK8Nyf^F%? z;6Vn!bm29hkxpyLO{1}vfv{*|;YLV`MyjY`huv|#C{F*3wkGf5AEPc(*-ZOkLfO&e3m;2sN+0s)B-HhbVVy( zvE5n^>y^>xyNO?S8ZITw;+Pfgi1tn;-k!bVD_wqQ>-oUyj?YiHh~$doe!300&8DWA zR3_g&DOJ-9!K+)S)QjAHZq(GNwW5|En;lF`8=i7#X&o$J$&zXkR<-!;6X>VH^nvpv z=3C%JoLCLD>$@C1#Be&v;AIrL$UM^)j<3Is%Waw8Z${hv`F~OhS77V?UQ?sk>L^M4_>#utB(g2$u?I`=;E^8nREM5 zO5GKJPsJBvXyqnr;Z;D6y{wG09?^-CVLGI8x^z+$cSQ|r-%7}R=v27Dysmb1!ZXJs z4kMs{m%^~#%Lm4baUeYR;n48?Q$LR8o4v(92bMsd{HJfnBN8pfuLkqhN@imrVcARL zXJfwbbLwAyB^1U`?C>FEwD$VsjZsMK{4%esmcTzodl5LSyCmT%Z1`nGwR(~6#(1Pt z8tqjUgDTA9ezsE4S+Vz;!nd~zn8~J#BP0@AMHWm+$!GB;IfVP3$k%n2-*HCO4^Mw# z4SfD~L6EvUzRcwf9m)C9drUWaD`Hc{BLNCz6g< zae+45Qc3H|z1;Uo!q4UeN(Bp}PNG}9H(%KJY;dTaDxtl=VWbek$z~N@S4pCDYRIzS z%oV;}N00ClbpO9%?_Ke$C0+L;vKV1HQN43a3p$jD@6=q@4!)V%+_v8%=}ujP%g|uO zBe3IV*>^7c0}an+IZ*qNn(*><{gw>rl~c+lTNRkF6BqUKYZQ-@J_^@YF;P)eFP8q6 zM?wIvpFObgmW_Tm_QALT@l9arqE?vEvPqpZ)1PV)P~_8SL1%yCKwZtE647A)R}mJy z-ifSrHL1w~jiP71p4C%MP{E0#{7Ui9eAj=WQGlK%*DXN7Zbt$7rtsvCAZtU~Bx+Xj)3*$?|bQ`n!Xb z@Dy5l_J)%2TCeiGOWfY`G(iyfgwhJyMu(uE0Fyw9AXpIcT&fQ77`1{;; zB4RBFil7kiXD{ym;4S-K_7-@`xXRZ^LD}{3LALCL{j^VICQPSssy}f3hW>YhI-$Dq zx$sK!xAVo#qZ-5QEsm@nl%9Ah7b^8v#|%io}8SksgGihf_`5fx*p zX2^MJ%48|5lWEjE+E)|2y>%rweQ_u$RWW7p!*rOecY^s{*Up-35WGE;m$4vm%Y{fw zNhPLGAkr?HsZ5c@-6>psR(}&fGuh(021LY{556WWn1)Jj7MSW@xk#pxQj2SpSmE97 z(Z1;KY|K~6KdF=72l@*PvYFSJqqJ7NBDMhAR>B%!!Sbm}Pf()5bIUl7@970T zHC)Vt+hWJs{zQfOgj8hHsy4!BencYF$mFW4FkCwxp#R!^#5p%H;W@C|Nt0DX1+6GP ziiycS`0>eI{+{IDDkdvk&D{Hue_NvBA1Ne%JfLo2pvFak&XAkYGQ6Uy(I)HYw;lo9 zwwFn`!hf{%>7$#62UR?7S!~3rhDHu0;+?OSFn$|5YoqsdgyU14s7+jR8}iWqnd(?z z=f`h{pdl67Z%>NIZH=hok+piXP3W7}jluE;!;J0EQ}tMgTYY|cYa#<;J?d<*2GHCsCD^ah#R#3DbSzB z_mPjjCvf%z6ispEl{YpNHoZEp+NDV?(h5`fViZ+W$f~+G@xcQ-_OKs*wP3jU<Yiz` zTPl4f>#!-4T}v3FVk(W}B7H&1W;#u2u}1rK1cxK|5e{cGT|9 z5WbGCSpIGzS_}@yVrQ&yhbeysK6-ZzP+8&#DzD)Y)I3CFs*mtoNyO$zo)PW%USc@9o?VvavQk;$j3+~ z#3n~jahAZH(;TdBwznx*)N;oBKf6-R!xzv;?V~>|BCr z!z~3RbW7AE8Tm;SZ(!KNTfuZh#5YzALX)^4NND!AlA+ zeJ+bj0hvk-xbm#^`X_W4AqYe=8ghfj=jX$^D++%OcuDw>_E_5MkCZWx=C>KaBj>l% z18+ibt1Aly!k>-p%-G%PLbmq%p6z>rRRSWYPJkBaUVpNufOgjZmELYSNoFMTS5CT7 z$^b95Q)e=KACg*9PUImvbV_ZA7Df=(`CP*qVZ;hM2|LwGJ1q!tIn61Ec&762d#mC3 zwFq(P@G%4o%NIO4O`zHc{)mFI!RUXY3MiwySUrNJy&Ots+V+20fIBdq=w5FV_B=l7ijDgJ4Nflim7Rp1@9I8sW48^q zZ{1BV;8GrQiO~2%ed+M~s;AYl=LW6Vx$TLz9ov!@hl%zcqI!7pdxj`tC;-_MR(Dvr z{nuoZVOv9>J7LQ{uYa;KWzq6g_`q6Pd&0XV3>3EqO)aPEFONr z{4F(#Midq{D$7!Xx)xLgr@d#pV@#^vO;RpK3SO6AVvK2XU$bc_edBb58f1%FDFeFItX|DP1L9lflpZg z+xoXmNtLrp$0+5jL5}OKBQlZc(>T2ihV%LG87hOQE@b%NsX^`E7jq33KFW7&1ZgI> zH=;-x>4xt33kqIw8M{4s5k41Cc>(1d`-ZJC?D+RG1rNMo7#>li>DXS9m`qka7!N%8 zr}2B+?<}ZMWp;sz{*U3V?&oWSf=~N(H5ByRWD0oxMgA=XpIFW|g6$wjXm!sryH7l7 zdZF8N`FOtluh$!jQj|Adga1FW-aDx2sOuJ1L5)gP}+WN>L;C^DAb&`8OIL3`poK|{~^-ng|cS+F+4=Ljc*ZT4%|Pjn?# zT&{&z94|-7>`j*(+tIY?$$w^baZOgtEzIO4rjz%cY&9f6)-9>=KAE`KxvEZfuX1ff z<--E^EGkQ8+@GIdu_dl#svwd`&I-G8819@U)~Azp^BzVf`ur@IctoQ;k?j?A_^yH2 z^1CSJF~;juekP^;?UZ5TPk4OE_WZ#sEn@iWTfw$X3}xQN`<2GoFrrQHHVF^-h&mk! z%X>0KiXoB(SNu;lHqSSH`JJCK89Mv3^tZ!D_jB)ov`9US68%Ae8|d>tfy7p_ca}~s zYC2YCs5)Xi&`dzz2?DLynydq2Tr0-ShE%&R*W?oJ3iEt~$%ofn`oR!`btGFt>a%0X zDxwap3K$WljAlP5oljjc%+dK^tFP6+k|ZdULBC`6+@a+uV>O0326r7@i|T!T)MBmW zA;NB0zdmkFSa6324r<5PEHnKZd5>I;~BmS05jtZ>@w(S>n!IJby9C}T}BrhzQ5N3ET1x+ zth+pi92^7~82Qtz+7Z*=G-`0wkdbdZE0QY&R~f<%6adh&B$yd*;X!-TSMa3wElj;% zh*03p&!!$(Q>nW91_4mIl+p~1SVJ}+*NTuP45a|>cLS<%sv(fLOx7zI{^nFtUln-9 zKJ)7A;xKfa&f;y^f-d^-XkSY4Y}R|tbmnoL7r714`kQ(~#A0uC|9bFYy7!sa2&$>! z)x_EOneXm^s!U%s_D*tt=@fmMw@AqSpr7^zmcxd83ce3jb;65J*=v2sCJ zi3oHJ+RSqrE=!kx?|LIN?r52*@z{wNmvLNi|5-n-X<~Z63MuWoUN-ZC_`rl^ueRwh z0egW7JnFK5$t{m@ADvD#9;LpysjnUT`N)fpR2!5c*e;y1hqBO zw$0CmB6Lx#U)<;Y6pzb4u0f>%n=edpL)f8M9e<9SPC;u7YpbFGyfLv;raw0DsCf&~ z?a|5jkL^2L$)5NP4fYsbJZb1zQndfho@>|OQ9-N74MW^$>+t+Q@j(MoF;(ECzg1%X z!x`7+<6b4 zR*Z9Q!Xrvk$rB)fUkmakJ}0k99!%>~=EV&7uGD|4C(A*;wrJSy?D&}6?nV(z{)5`z z5~bNMxX((ETIj&t>~UjNoWuh)XYL_(7%^L!sW9)ybd)0()akyARVMYxOD{Br zSfi*AV(oRmH&d8i$iFmZF;AW`c9SSV!8*~iS0bP`R~P+0YqUbstb#VnBwkivWEbXw z3RIEPMxo#kCg=VPW|4IO9&*Pfot+SZ(w+Ou6zuEt+#{gUJApIb;@-jYfdXsO&$M@t zugt#!N+FCoM}rpj8H=6!YMj@UlquP46Z$2e*R6^6?s~6&o7FhgL0)cFqT@@ku=Rg1 zstojf7V^yDwjui+0cu!&Hk|h%=waXepW&hO6V67SZ_4ob+Q*uE)PURCI7dGLqo~xLF%X3|L~C#>vrvkE@Ty}oVW<=Hfqlkgw)b&aGv+8G>OBMsW^Vc%os6} z8f^8?z@hX9SVw!Pp~&Sf2BjNVQ z9L)g5?&q9g2+IL7;s<_w`Q@qZF=uYw&;v5O`_nNYLf~Topb2JBYui}%^!wUMu3lJEF&L%% zl1HJP$GQNyQ!<<3?2s1Zcf@`WH9JW38+kG_tu4Qo{tG9p5D_;!DBvd!7VsC{zUAhp zcbqgr4sq`}8?zrYkkua;#N5c!S;Ta5X#0u#`DgcJ^Z5P#F_~9#*5_2LK2h@X8DW-8 zcGIh!ZDslHR9p{SKhGsUB=>GtxplK;d;DBw9YdW92Q)i!l>}i&lVfXQk6(Ed;3(sYr2fZ!qI-C zNM00=hk6kklcMnLED+Z6+j0Z^H5@GX@^;rJ3;TFgg#;V@dj|MB+Sc|m0;BD)wBSWt zBY(IIIJ3EL+R0PixCa(0!=1na9*-Me=3PO# z-mqq9mH2i$(lHNTiORB-h+kE_`zyIi&&#!qJ3*}}@O}&( zTMUkpOmO%2yBJslxu4q`UX2T}tH~Dfq;R_9K@I4whkrNfJj^3NRF+vt)M4MYv4%4u zg(~iy%dtgB@ag*4?_)qYsU`~R`@<4PRzH?7(4EmeFBs1eAK(AAC8Tss;3=h)=UpUa zT9VWySwRsBW`Py&1(a!Oi%PJ(G7w08-uJ>5I)`L<<=y?3@&)ah=#d`{b6oP?+?V!k zAX`GbuXfFVElJXEib2vF_q1QaRc;LNNOM?DWyu$g%i4B{y^EG{6~aBE!?A0kFB%^7 zpTDw+v{uwG;jpONmrr!p-U9YQBU6V?rS2w1Sf9`aZ9ONl_4#?%p6(<@EGK{6UIJMB z7)0i&ivOYf^Nc(V*DJfSzI$~JGm|HOM&{@>oN>+pzI}O9tv4@4aMM%e#E|U0TWU0Z5JEVwC5U}U zC<#6zBiEZJ&ktNy6#j>tQC#=^XDMk6`OuLp%+V-+TE4n{um4V1G$8PA7qJZOA5gpK znfSpRY~3Su(ONN14#$<|K4*Kb+-m`Idp|u}TxpukDH}%WwN0r`cm>q*a4nhM5U&z+ zq!msPBdxE09bDun)7Oq6G%*tQ4wh0-$P50-i>HI~E zLa8(tjyq-r|B{KfF4%bHjfHM-yN~=v{&ClGCH@78QyV6UQgZ@+^f5sO`cY}5e937-G1 z8-IF(eqV?Xuy}?bMBI*c(i=OL4SlCydd#m z8^HfaWE^U76vf`y=eu9Q+&FqjoH-}97JQ>&=RV$2WkwqoFMN-yj;UZt@@$wx zKw;(bem`GP)=jlj*Ou|0q@Db&mD)UbPW=8`+dM4>XTikTyr}l-$;&?5ot}m}6(<#d zmGZSHAAQICb_MhtN+IIy>?GFXFiC5F8ho1cV`{c4?egX+epGYcR(n%5D#4n4F0xX9ou_dj zExsMvoWj6J19lX1V0Ymw)=$+P;GfuqfAI~5RQfP^8ZC|1jv4ND*&R|17P$qkx!4z_ zlom(jr^bF^O+Vod{&sfGU1ys9=J8Ry-uO=X$-4JJ?U!Ft=5FgczYvd@^@Spo4f z?O~a^H3Z)`CX@rEhOOP9=OvX&maOxU$PA&FA78eQ4ha`;?LuFm!^jB5K5->@!{MLQ zO$nLcjd+1qR@6Fd@w~9{-#k(5Z`k(?@Z6VD}Z3GV+x8`l;T6^bA zRcoSJ4^#rd6!gOZ2>=ogJ*>bqTcW!W7TA0G&Iz_=I@AFp7Q*xAoyJ%Qtf_);;9jqH z(**#%QqwX&735&uo^#%g9H~--xF{BYw#vGyS;e>_1F*d2V1s+_dLsdGln{|HFPsc9 zQjHYPpY3&`aKUWNb+S&{vvYa3AUdS4aa`Yse@5hbGWC7|jNsES(QG(jW&NHcu0n|x zy~Nv`JE23j>Gve)HRm|%S?9IuncI-~5Am-u>46KzV@6ECgdTBZfyNs>mpgiGlosu6 zq~_Fn=O$Tm);p3G_JGH`<6w_M`j`@&9hBZiZg3ez(mC?_F<{|YfHP&5aZKm^ zVVQHmn^`2^Mp_9zpP68?I8-{xn;~)1Y@Y_p%@SHv$D>POK}SnOq{ZpWS~8G(^UG)P zH+~0t+OV`&k5@leo2BGuD=M6fA;-BJR^*8gGH;gLN3p@l3iDD>cge=Dvv1D! zMgVGO?XS6`XUOVEy<`qxATd4;qG5x%%R(-iA=N&OC$ubUtALAbz{JG~`eJkgKFLYe zE%C12FCq6sk#RyZ`z|vE#9o)`iM7!#8F^P>^u3K`Kb@~cd+k4-XRC%KZo#AgBIt$IYB!H6Boxm06B2wAP)-Xgm|2PV{(M&kH_EZSOQW+3+%XSJ8B} z_Eb(c+q%mt0$<&A*yFd2J6n#4QdT_s; zi))!0Esi>eh`(L{%&AahC(?`2$89qw@&Z*hX!4g@x0f*m7J5&j*J^Cu>*3jsgm<2%NTa^{1zM?oW}l6T=ymrby15$!0Bvz zjKYqZ+;H7A3nKkpYuc3~l)&s3e98b1s2i~me3R5G)GU6p`-`Na|?wvY1u!)`Dx;Y31bWbo7_nGx-125ewao4?iWgaS4lUta4 zlsx zjR|=iEW22BPZ+6ghPJnkt3=}-6lxuPzuw!k`fB;UaLi{}>6c6w@kzLh9>K~C& z^+FW!E0CSQi{S74MvUE*ocJY|LLr{PKX(hq3C9%0VWKQ^UX|WQK@j(rz19FCx*I(=++^?2t-gFo4E>R>yPh{!&gW(#2p;iV1nqSjXBdbtyAPiar7Lbv7k>5u1wZaJ8NoBzI@ z#klX{8%LCZ?A(W(L989`vyP2tb0J4phPx8-RQvgtQlwHaX zcjpP%gOimu&bm1JKM|3NP~~L1LZC)gi*K^79r~-SPX(Ac4SX>;0M`qMJ~((} z2OGNRfZlw9f>e~^WLVs!^l1$7D2-QR{H#Y)sd|FuR z+JF1>{7MM9wesjx+*E>BocAkTyCZ7@mq&3S0GfDzZpnnHL})F0ocRZmZjn=2?=*#d zq+iY3MXqGIM;kA=URqTYX{zgjtlIg_9QiCPRkRky<2rw2H77M&4P_VK7_3R2)nRjR zY)p{OoS$A;U^0z;KKppq1H_fx=wY@n#pQ;X7b8rF!zDQOE5fehB783912wN)5bvbH zElWzGJY^BS_xve?Nk?SChOEJBf6NP}?y&WbX`176znJ_QKoIC+Bs7gZ2m-@d~M*>3lBft!mB~-%_y~ zQZRwK%NPYvtieS7QAK|aeYi>Rq48%*2|8a790@aj&lIc6&3mS`cOqFPJM)CRNA6p6 zNmujUZ@wQ3WeZ8ApfDnk0z{;#;`SZSFvp*j>C_$|A1Bvi_;HUFeiplTIKqlmv3oU` zYE+skK-^&(KPeSu;Z;)UU^ch{jj8Cq`?Bt()p}2DCv{EkK}~mlKGV<%`v{O+FHmAQ#pQZr}x-OqXA{+cOe%nW~XTf1DFU57g$lzChV#!pD5 zR#6JL&3U$99_S}L!l)0WIzJF3!D~h(jAI_!qW?IY8O`rF8(5DD78_f`D*JIEIj1-t zweYx~Fs%m~?gdc}(OREBmeu=xPAY3J)deesmy{dh>G&^CuX~6&fIkT9Z}(XCRaqbw zwGtWPS-1y4LrEs~@=!w3L=ROr6~|qwZnDDJT*O!uTS)7z%OwU$FQ01?U(o476`_uT zB_*9JW{Q3sktZ#8$B$=I49?7p>Y=s5*XR9tW16#|e4j;(1i5;o;dX_E_WRDy)>V~( z(&+*zwhbJ~8T4Cn5=AX+C-tYRj6*ZRr3J?cD@JrNgU2P4GT=X*r%=-MgR{=~)%2W_h#HFovpKB(c1F}$;PPm%EvA){BlBd$@i^Ts*DSIjRqngWcaY3bs$OlvB3QfEYhdrusk@zSdbt z-pW1nhYEIpv>!nrWcdNSkH5zgK9CUCqpSX~73S+~y#sd_54R3~sYM%*miO9i{3m}} zcg2t$>OUpTQ*IK^Ky0aMDjW4UR1BUavnW)5TggTmP0&eIgHkG~iaFIfB$g}Er|G}h z9khTvMFI(qn!c&us;NCrRgllmG%UeiNgp_oszE{-d~Ta|5ioiv4QTw5`_9gdtCfF6 zF6@-_mK95M?jkk|@aFat0f8!(DzoiMmdt4}qQSit$#kGey8J;v0+8;a*{?b(Q59D2 z!E^t`P?|G?C)%+d=~YnPvw(WVuuT~TeGm`b>Mq>{F%J`%vS4L=J76zO^oqyDq{Rb4 z1EOJF^MdrG!;otXTJ58Go6I4F$A&5%72$9($?$vqZe#b8R#QC18>!(3>uwy7qCU}U z%%OT<@jnca<3cMF@||!>U1}+nUr=%POTr#h#GWQY#0x&*y`9hD)Z?kt^vUuGYq32r zCBqDn5I!0?=|&;;jA}~KZ~gzy00MD!Cb)pilgjrTu6zQ+mFJ!XtB?x%nG} zyD5!wDi-2Cm`JL&D>e4qOP;!#%GMN~A{>Su&thInJ1lA#7skL07esCv1D=LlbJ-69 z!>qzRzJgu@Sr}itbFwaTO-JSlgbQhXv!*Q}h^GI5v?jagbLfv%5Mv($=xiC->SF+2 zJIoQXSFTVKp3Um0b5w#ul>iS8D4t$zpnkpDZtsRhJbdvd+Y>>_;?_Ec&!@N^Cx64) zp8KF4<6H0`I>VP_8PtrrPHa=QBv&)2?sCzzWYwCo>IXcJf!7x^5A;U zpEbePLjIVrcwvhEX-6~Y1gf~?$60h!bW|+%!)alQY*7J#2!(-~aAUcHfhVEJ4n-Hy7 z8yZddpPP5=-_09KobmORqKIIp;N9zb+!$&9#uy*FW?aH=W>8c9Q|7T2PWt@B1alvaK(@+SG_wkM5Dwl z2#svlPyGi4!ntiCGHmMny-Q1R2c-`!*K$I|2-GDuF}h~&S+SEb3%;qSLp4tNKF!NJ z{5c28UjFHBes%pK_5Gx$^HulCe*HK5p@XR+H?BL9Je3NgWJPthIOIcadMx?7 zyCx(SlUgE=i;v&2piBaW!--2x1aCng$v$_vXbTU{U3MjP%%jFaNGUG)XX%}nWFD1BfTt<3?1o+uSwlZCOSOAYC(sS9{xDgY+d^Z)MOCE(Oqb8Ebn?!`B7Rm0$ z8K+(sH`45cZwrrH>Fro>8@{%7C|@MDe&BeX57?zfD?XM%XXhtDN$=Ct5w;u)!mv9T zx+f>~&WZD3oe;r$WqtNXH8WNcSqy#!En|LD^)b#iOl1j5;qtrt$_YL%XjvSO>$1VZcfR{Z(!Pf zI`$}@))FgjaES2`eaFM&rmlMRfsK^iLs&F?-w*UNRq=3Uhfg4linj9a8_CXjSkbK* zA;?=jb!v$*6P8qozWsTxHc^qU<)JoF7sPbubvkY_8$!V5zq2?O@$W1KDWr;}(}DEq zSPN7O&pR9LuoL6T3I?4pw?;m&Ef0V+?zQ+aOXx@!9GtnKoN=Ku7`yOx88c z-v46=dH2jnUAmusgRDCuR6O0M*bW2%cJi(p6w13aJ%DG}cio5mDtUKc23|59v= zjT(zWX9LOF_vH({2YuueXq}6EuYdP9>f`;F5NWF3hzndptb2) z6ZW3rr=02}ab0B_2;DmsqQ%u`&}PZ2dVSuIBr9%%m6${9y}f0raWa^xrW8G&N$->< z>Kp4ZZrkO5Qb_bd)6>IN_rbx;n`}S&lqsZHr0w--R9kNLLQr7`7>m&7rjn%lt-?_kH)fho z2CAeHwzPcw@h4)lljC+*334tX)BEwhqC)4wCp?#K!G z*m9)rD8Uth4~tDP@Q|%y=*TD0lmPdg5mdxTR-9MeL4!bms9JqBaNg7ixw8OX7YVSC z@C20f9@OYUXLU$@b=PH|w#nob=0$Bu*eM7H$@O-5s|I|Wz_2>pX2v^zV6)Di7U@n) zUn#8Nvi^89jHETIjeTgnU!c00WIAqEJ?4xa1XJjM^DXa{C)R=;lPLN<-R&pZL${AR zfiyQ&s-{fW6T-~s?Bk8*#gyEF#^zTn7r*Wo8!qQFOCFo>UHq||1^VpmU^zU~c881{ zT^;Xx1lUtQ+-C2y!0R&}=vudZ_{<~@^JqqVOTl4CvrHC4UPhb_t}_1$B-?x9G}@}+ z+$+Z9f?$=Z5Ocd=S8t9s0mzn{uJg@N-p=RSXWaO2N0Za&V54g;G0OMApX>eVs(XS2 z38~y8OJgta13`Fsxmqa;6byYDHmQWLOklP1*FW1pfY=VYmsdS%q7Dnzmk(Tg&AO32 zy8g)%Wr{|p&0aPa1_C`i1ELiJNK=^JsvDq}xPyPuDoYp5n)7b+UfDZ*43aWWYylW| z^_1kJ(Fx`c&yaDO!GDDHl-O zF3R5M1DEU8t7k1KW4|uYJgh922Cn7Or&Lp0mXn4PBfxeGI_xp@Rq&wf#;1u)GI|TI z8{Gco!nyyFn5jo+3VnVmvuaPjnq&xZHSI}w#uswokJDwN$rGt⁣#RRTHPRz~nCL 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

    8%SDM;z--8OkU70)> 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

    TwNM$WulyQU!* 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

    8(O~S(`*carfOz+TKRGXq(K| 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 literal 0 HcmV?d00001 diff --git a/modules/cloud-config-container/bindplane/main.tf b/modules/cloud-config-container/bindplane/main.tf new file mode 100644 index 0000000000..a2a11f8654 --- /dev/null +++ b/modules/cloud-config-container/bindplane/main.tf @@ -0,0 +1,48 @@ +/** + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +locals { + cloud_config = templatefile(local.template, merge(var.config_variables, { + bindplane_prometheus_image = var.bindplane_config.bindplane_prometheus_image + bindplane_server_image = var.bindplane_config.bindplane_server_image + bindplane_transform_agent_image = var.bindplane_config.bindplane_transform_agent_image + files = local.files + password = var.password + remote_url = var.bindplane_config.remote_url + runcmd_pre = var.runcmd_pre + runcmd_post = var.runcmd_post + users = var.users + uuid = random_uuid.uuid_secret.result + })) + files = { + for path, attrs in var.files : path => { + content = attrs.content, + owner = attrs.owner == null ? var.file_defaults.owner : attrs.owner, + permissions = ( + attrs.permissions == null + ? var.file_defaults.permissions + : attrs.permissions + ) + } + } + template = ( + var.cloud_config == null + ? "${path.module}/cloud-config.yaml" + : var.cloud_config + ) +} + +resource "random_uuid" "uuid_secret" {} diff --git a/modules/cloud-config-container/bindplane/outputs.tf b/modules/cloud-config-container/bindplane/outputs.tf new file mode 100644 index 0000000000..56e0824296 --- /dev/null +++ b/modules/cloud-config-container/bindplane/outputs.tf @@ -0,0 +1,20 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +output "cloud_config" { + description = "Rendered cloud-config file to be passed as user-data instance metadata." + value = local.cloud_config +} diff --git a/modules/cloud-config-container/bindplane/variables.tf b/modules/cloud-config-container/bindplane/variables.tf new file mode 100644 index 0000000000..39856f4585 --- /dev/null +++ b/modules/cloud-config-container/bindplane/variables.tf @@ -0,0 +1,88 @@ +/** + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "bindplane_config" { + description = "Bindplane configurations." + type = object({ + remote_url = optional(string, "localhost") + bindplane_server_image = optional(string, "us-central1-docker.pkg.dev/observiq-containers/bindplane/bindplane-ee:latest") + bindplane_transform_agent_image = optional(string, "us-central1-docker.pkg.dev/observiq-containers/bindplane/bindplane-transform-agent:latest") + bindplane_prometheus_image = optional(string, "us-central1-docker.pkg.dev/observiq-containers/bindplane/bindplane-prometheus:1.56.0") + }) + default = {} + nullable = false +} + +variable "cloud_config" { + description = "Cloud config template path. If null default will be used." + type = string + default = null +} + +variable "config_variables" { + description = "Additional variables used to render the cloud-config and Nginx templates." + type = map(any) + default = {} +} + +variable "file_defaults" { + description = "Default owner and permissions for files." + type = object({ + owner = string + permissions = string + }) + default = { + owner = "root" + permissions = "0644" + } +} + +variable "files" { + description = "Map of extra files to create on the instance, path as key. Owner and permissions will use defaults if null." + type = map(object({ + content = string + owner = string + permissions = string + })) + default = {} +} + +variable "password" { + description = "Default admin user password." + type = string +} + +variable "runcmd_post" { + description = "Extra commands to run after starting nginx." + type = list(string) + default = [] +} + +variable "runcmd_pre" { + description = "Extra commands to run before starting nginx." + type = list(string) + default = [] +} + +variable "users" { + description = "List of additional usernames to be created." + type = list(object({ + username = string, + uid = number, + })) + default = [ + ] +} diff --git a/modules/cloud-config-container/bindplane/versions.tf b/modules/cloud-config-container/bindplane/versions.tf new file mode 100644 index 0000000000..bc9986b3c7 --- /dev/null +++ b/modules/cloud-config-container/bindplane/versions.tf @@ -0,0 +1,27 @@ +# Copyright 2024 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 +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +terraform { + required_version = ">= 1.7.4" + required_providers { + google = { + source = "hashicorp/google" + version = ">= 5.26.0, < 6.0.0" # tftest + } + google-beta = { + source = "hashicorp/google-beta" + version = ">= 5.26.0, < 6.0.0" # tftest + } + } +} From a14ed9add2777769d6b1657ff1b6c44637d2e86f Mon Sep 17 00:00:00 2001 From: luigi-bitonti <93377317+luigi-bitonti@users.noreply.github.com> Date: Tue, 14 May 2024 14:56:10 +0200 Subject: [PATCH 28/31] Cloud function CMEK key support (#2270) * Added support to kms key * Updated doc * Fix variable description. * Updated README * Cloud function v2 integration with kms * Fix variables description --------- Co-authored-by: Ludovico Magnocavallo --- modules/cloud-function-v1/README.md | 47 ++++++++++++++++++++------ modules/cloud-function-v1/main.tf | 14 ++++---- modules/cloud-function-v1/variables.tf | 17 ++++++++++ modules/cloud-function-v2/README.md | 23 +++++++------ modules/cloud-function-v2/main.tf | 11 +++--- modules/cloud-function-v2/variables.tf | 6 ++++ 6 files changed, 85 insertions(+), 33 deletions(-) diff --git a/modules/cloud-function-v1/README.md b/modules/cloud-function-v1/README.md index 7099946e2d..ebcf0dd0aa 100644 --- a/modules/cloud-function-v1/README.md +++ b/modules/cloud-function-v1/README.md @@ -16,6 +16,7 @@ The GCS object used for deployment uses a hash of the bundle zip contents in its - [Private Cloud Build Pool](#private-cloud-build-pool) - [Multiple Cloud Functions within project](#multiple-cloud-functions-within-project) - [Mounting secrets from Secret Manager](#mounting-secrets-from-secret-manager) + - [Using CMEK to encrypt function resources](#using-cmek-to-encrypt-function-resources) - [Variables](#variables) - [Outputs](#outputs) @@ -258,6 +259,28 @@ module "cf-http" { } # tftest modules=1 resources=2 inventory=secrets.yaml ``` + +### Using CMEK to encrypt function resources +This encrypt bucket _gcf-sources-*_ with the provided kms key. The repository has to be encrypted with the same kms key. + +```hcl +module "cf-http" { + source = "./fabric/modules/cloud-function-v1" + project_id = "my-project" + region = "europe-west1" + name = "test-cf-http" + bucket_name = "test-cf-bundles" + bundle_config = { + source_dir = "fabric/assets" + output_path = "bundle.zip" + } + kms_key = "projects/my-project/locations/europe-west1/keyRings/mykeyring/cryptoKeys/mykey" + repository_settings = { + repository = "projects/my-project/locations/europe-west1/repositories/myrepo" + } +} +# tftest modules=1 resources=2 +``` ## Variables @@ -265,9 +288,9 @@ module "cf-http" { |---|---|:---:|:---:|:---:| | [bucket_name](variables.tf#L26) | Name of the bucket that will be used for the function code. It will be created with prefix prepended if bucket_config is not null. | string | ✓ | | | [bundle_config](variables.tf#L44) | Cloud function source folder and generated zip bundle paths. Output path defaults to '/tmp/bundle.zip' if null. | object({…}) | ✓ | | -| [name](variables.tf#L109) | Name used for cloud function and associated resources. | string | ✓ | | -| [project_id](variables.tf#L124) | Project id used for all resources. | string | ✓ | | -| [region](variables.tf#L129) | Region used for all resources. | string | ✓ | | +| [name](variables.tf#L115) | Name used for cloud function and associated resources. | string | ✓ | | +| [project_id](variables.tf#L130) | Project id used for all resources. | string | ✓ | | +| [region](variables.tf#L135) | Region used for all resources. | string | ✓ | | | [bucket_config](variables.tf#L17) | Enable and configure auto-created bucket. Set fields to null to use defaults. | object({…}) | | null | | [build_environment_variables](variables.tf#L32) | A set of key/value environment variable pairs available during build time. | map(string) | | {} | | [build_worker_pool](variables.tf#L38) | Build worker pool, in projects//locations//workerPools/ format. | string | | null | @@ -277,14 +300,16 @@ module "cf-http" { | [https_security_level](variables.tf#L85) | The security level for the function: Allowed values are SECURE_ALWAYS, SECURE_OPTIONAL. | string | | null | | [iam](variables.tf#L91) | IAM bindings for topic in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [ingress_settings](variables.tf#L97) | Control traffic that reaches the cloud function. Allowed values are ALLOW_ALL, ALLOW_INTERNAL_AND_GCLB and ALLOW_INTERNAL_ONLY . | string | | null | -| [labels](variables.tf#L103) | Resource labels. | map(string) | | {} | -| [prefix](variables.tf#L114) | Optional prefix used for resource names. | string | | null | -| [secrets](variables.tf#L134) | Secret Manager secrets. Key is the variable name or mountpoint, volume versions are in version:path format. | map(object({…})) | | {} | -| [service_account](variables.tf#L146) | Service account email. Unused if service account is auto-created. | string | | null | -| [service_account_create](variables.tf#L152) | Auto-create service account. | bool | | false | -| [trigger_config](variables.tf#L158) | Function trigger configuration. Leave null for HTTP trigger. | object({…}) | | null | -| [vpc_connector](variables.tf#L168) | VPC connector configuration. Set create to 'true' if a new connector needs to be created. | object({…}) | | null | -| [vpc_connector_config](variables.tf#L178) | VPC connector network configuration. Must be provided if new VPC connector is being created. | object({…}) | | null | +| [kms_key](variables.tf#L103) | Resource name of a KMS crypto key (managed by the user) used to encrypt/decrypt function resources in key id format. If specified, you must also provide an artifact registry repository using the docker_repository field that was created with the same KMS crypto key. | string | | null | +| [labels](variables.tf#L109) | Resource labels. | map(string) | | {} | +| [prefix](variables.tf#L120) | Optional prefix used for resource names. | string | | null | +| [repository_settings](variables.tf#L140) | Docker Registry to use for storing the function's Docker images and specific repository. If kms_key is provided, the repository must have already been encrypted with the key. | object({…}) | | {…} | +| [secrets](variables.tf#L151) | Secret Manager secrets. Key is the variable name or mountpoint, volume versions are in version:path format. | map(object({…})) | | {} | +| [service_account](variables.tf#L163) | Service account email. Unused if service account is auto-created. | string | | null | +| [service_account_create](variables.tf#L169) | Auto-create service account. | bool | | false | +| [trigger_config](variables.tf#L175) | Function trigger configuration. Leave null for HTTP trigger. | object({…}) | | null | +| [vpc_connector](variables.tf#L185) | VPC connector configuration. Set create to 'true' if a new connector needs to be created. | object({…}) | | null | +| [vpc_connector_config](variables.tf#L195) | VPC connector network configuration. Must be provided if new VPC connector is being created. | object({…}) | | null | ## Outputs diff --git a/modules/cloud-function-v1/main.tf b/modules/cloud-function-v1/main.tf index b57d8431cb..7a7fdc9152 100644 --- a/modules/cloud-function-v1/main.tf +++ b/modules/cloud-function-v1/main.tf @@ -41,6 +41,7 @@ locals { ) } + resource "google_vpc_access_connector" "connector" { count = try(var.vpc_connector.create, false) == false ? 0 : 1 project = var.project_id @@ -67,12 +68,13 @@ resource "google_cloudfunctions_function" "function" { labels = var.labels trigger_http = var.trigger_config == null ? true : null https_trigger_security_level = var.https_security_level == null ? "SECURE_ALWAYS" : var.https_security_level - - ingress_settings = var.ingress_settings - build_worker_pool = var.build_worker_pool - build_environment_variables = var.build_environment_variables - - vpc_connector = local.vpc_connector + ingress_settings = var.ingress_settings + build_worker_pool = var.build_worker_pool + build_environment_variables = var.build_environment_variables + kms_key_name = var.kms_key + docker_registry = try(var.repository_settings.registry, "ARTIFACT_REGISTRY") + docker_repository = try(var.repository_settings.repository, null) + vpc_connector = local.vpc_connector vpc_connector_egress_settings = try( var.vpc_connector.egress_settings, null ) diff --git a/modules/cloud-function-v1/variables.tf b/modules/cloud-function-v1/variables.tf index 73964d0b18..8dc592addd 100644 --- a/modules/cloud-function-v1/variables.tf +++ b/modules/cloud-function-v1/variables.tf @@ -100,6 +100,12 @@ variable "ingress_settings" { default = null } +variable "kms_key" { + description = "Resource name of a KMS crypto key (managed by the user) used to encrypt/decrypt function resources in key id format. If specified, you must also provide an artifact registry repository using the docker_repository field that was created with the same KMS crypto key." + type = string + default = null +} + variable "labels" { description = "Resource labels." type = map(string) @@ -131,6 +137,17 @@ variable "region" { type = string } +variable "repository_settings" { + description = "Docker Registry to use for storing the function's Docker images and specific repository. If kms_key is provided, the repository must have already been encrypted with the key." + type = object({ + registry = optional(string) + repository = optional(string) + }) + default = { + registry = "ARTIFACT_REGISTRY" + } +} + variable "secrets" { description = "Secret Manager secrets. Key is the variable name or mountpoint, volume versions are in version:path format." type = map(object({ diff --git a/modules/cloud-function-v2/README.md b/modules/cloud-function-v2/README.md index a5bad718e0..f54dd50aec 100644 --- a/modules/cloud-function-v2/README.md +++ b/modules/cloud-function-v2/README.md @@ -281,9 +281,9 @@ module "cf-http" { |---|---|:---:|:---:|:---:| | [bucket_name](variables.tf#L26) | Name of the bucket that will be used for the function code. It will be created with prefix prepended if bucket_config is not null. | string | ✓ | | | [bundle_config](variables.tf#L38) | Cloud function source folder and generated zip bundle paths. Output path defaults to '/tmp/bundle.zip' if null. | object({…}) | ✓ | | -| [name](variables.tf#L103) | Name used for cloud function and associated resources. | string | ✓ | | -| [project_id](variables.tf#L118) | Project id used for all resources. | string | ✓ | | -| [region](variables.tf#L123) | Region used for all resources. | string | ✓ | | +| [name](variables.tf#L109) | Name used for cloud function and associated resources. | string | ✓ | | +| [project_id](variables.tf#L124) | Project id used for all resources. | string | ✓ | | +| [region](variables.tf#L129) | Region used for all resources. | string | ✓ | | | [bucket_config](variables.tf#L17) | Enable and configure auto-created bucket. Set fields to null to use defaults. | object({…}) | | null | | [build_worker_pool](variables.tf#L32) | Build worker pool, in projects//locations//workerPools/ format. | string | | null | | [description](variables.tf#L47) | Optional description. | string | | "Terraform managed." | @@ -292,14 +292,15 @@ module "cf-http" { | [function_config](variables.tf#L65) | Cloud function configuration. Defaults to using main as entrypoint, 1 instance with 256MiB of memory, and 180 second timeout. | object({…}) | | {…} | | [iam](variables.tf#L85) | IAM bindings for topic in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [ingress_settings](variables.tf#L91) | Control traffic that reaches the cloud function. Allowed values are ALLOW_ALL, ALLOW_INTERNAL_AND_GCLB and ALLOW_INTERNAL_ONLY . | string | | null | -| [labels](variables.tf#L97) | Resource labels. | map(string) | | {} | -| [prefix](variables.tf#L108) | Optional prefix used for resource names. | string | | null | -| [secrets](variables.tf#L128) | Secret Manager secrets. Key is the variable name or mountpoint, volume versions are in version:path format. | map(object({…})) | | {} | -| [service_account](variables.tf#L140) | Service account email. Unused if service account is auto-created. | string | | null | -| [service_account_create](variables.tf#L146) | Auto-create service account. | bool | | false | -| [trigger_config](variables.tf#L152) | Function trigger configuration. Leave null for HTTP trigger. | object({…}) | | null | -| [vpc_connector](variables.tf#L170) | VPC connector configuration. Set create to 'true' if a new connector needs to be created. | object({…}) | | null | -| [vpc_connector_config](variables.tf#L180) | VPC connector network configuration. Must be provided if new VPC connector is being created. | object({…}) | | null | +| [kms_key](variables.tf#L97) | Resource name of a KMS crypto key (managed by the user) used to encrypt/decrypt function resources in key id format. If specified, you must also provide an artifact registry repository using the docker_repository_id field that was created with the same KMS crypto key. | string | | null | +| [labels](variables.tf#L103) | Resource labels. | map(string) | | {} | +| [prefix](variables.tf#L114) | Optional prefix used for resource names. | string | | null | +| [secrets](variables.tf#L134) | Secret Manager secrets. Key is the variable name or mountpoint, volume versions are in version:path format. | map(object({…})) | | {} | +| [service_account](variables.tf#L146) | Service account email. Unused if service account is auto-created. | string | | null | +| [service_account_create](variables.tf#L152) | Auto-create service account. | bool | | false | +| [trigger_config](variables.tf#L158) | Function trigger configuration. Leave null for HTTP trigger. | object({…}) | | null | +| [vpc_connector](variables.tf#L176) | VPC connector configuration. Set create to 'true' if a new connector needs to be created. | object({…}) | | null | +| [vpc_connector_config](variables.tf#L186) | VPC connector network configuration. Must be provided if new VPC connector is being created. | object({…}) | | null | ## Outputs diff --git a/modules/cloud-function-v2/main.tf b/modules/cloud-function-v2/main.tf index c38f863950..24046eada2 100644 --- a/modules/cloud-function-v2/main.tf +++ b/modules/cloud-function-v2/main.tf @@ -59,11 +59,12 @@ resource "google_vpc_access_connector" "connector" { } resource "google_cloudfunctions2_function" "function" { - provider = google-beta - project = var.project_id - location = var.region - name = "${local.prefix}${var.name}" - description = var.description + provider = google-beta + project = var.project_id + location = var.region + name = "${local.prefix}${var.name}" + description = var.description + kms_key_name = var.kms_key build_config { worker_pool = var.build_worker_pool runtime = var.function_config.runtime diff --git a/modules/cloud-function-v2/variables.tf b/modules/cloud-function-v2/variables.tf index af97650fc3..428ba8dba2 100644 --- a/modules/cloud-function-v2/variables.tf +++ b/modules/cloud-function-v2/variables.tf @@ -94,6 +94,12 @@ variable "ingress_settings" { default = null } +variable "kms_key" { + description = "Resource name of a KMS crypto key (managed by the user) used to encrypt/decrypt function resources in key id format. If specified, you must also provide an artifact registry repository using the docker_repository_id field that was created with the same KMS crypto key." + type = string + default = null +} + variable "labels" { description = "Resource labels." type = map(string) From c854057bef191748c6fd66329dc9392c2152405e Mon Sep 17 00:00:00 2001 From: Ludo Date: Tue, 14 May 2024 15:01:43 +0200 Subject: [PATCH 29/31] update changelog --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c2177cdbc..8a34721182 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,8 @@ All notable changes to this project will be documented in this file. ### FAST +- [[#2267](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2267)] Fix 0-bootstrap iam_by_principals not taking into account all principals ([wiktorn](https://github.com/wiktorn)) +- [[#2263](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2263)] Update docs - gcp-network-admins -> gcp-vpc-network-admins ([wiktorn](https://github.com/wiktorn)) - [[#2260](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2260)] Remove data source from folder module ([ludoo](https://github.com/ludoo)) - [[#2253](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2253)] Misc FAST fixes ([juliocc](https://github.com/juliocc)) - [[#2235](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2235)] Update FAST logging ([juliocc](https://github.com/juliocc)) @@ -53,6 +55,13 @@ All notable changes to this project will be documented in this file. ### MODULES +- [[#2270](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2270)] Cloud function CMEK key support ([luigi-bitonti](https://github.com/luigi-bitonti)) +- [[#2272](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2272)] New Bindplane cloud-config-container setup ([simonebruzzechesse](https://github.com/simonebruzzechesse)) +- [[#2269](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2269)] Implement the full IAM interface for tags ([ludoo](https://github.com/ludoo)) +- [[#2268](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2268)] Add logging settings to folder module ([ludoo](https://github.com/ludoo)) +- [[#2242](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2242)] CloudSQL PSC Endpoints support ([wiktorn](https://github.com/wiktorn)) +- [[#2265](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2265)] Fix failing E2E net-vpc test ([wiktorn](https://github.com/wiktorn)) +- [[#2264](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2264)] Fix bug from output typo in new project-factory module ([JanCVanB](https://github.com/JanCVanB)) - [[#2262](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2262)] Make Simple NVA route IAP traffic through NIC 0 ([juliocc](https://github.com/juliocc)) - [[#2261](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2261)] Add Hybrid NAT support ([juliocc](https://github.com/juliocc)) - [[#2260](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2260)] Remove data source from folder module ([ludoo](https://github.com/ludoo)) From ff6f6bb32af76c73a9df4c68fb021bcd6bff8f95 Mon Sep 17 00:00:00 2001 From: apichick Date: Tue, 14 May 2024 16:53:38 +0200 Subject: [PATCH 30/31] Added apigee-x-foundations blueprint (#2274) --- blueprints/README.md | 2 +- blueprints/apigee/README.md | 8 +- .../apigee/apigee-x-foundations/README.md | 481 +++ .../apigee/apigee-x-foundations/apigee.tf | 37 + .../apigee/apigee-x-foundations/diagram1.png | Bin 0 -> 37153 bytes .../apigee/apigee-x-foundations/diagram2.png | Bin 0 -> 52665 bytes .../apigee/apigee-x-foundations/diagram3.png | Bin 0 -> 33258 bytes .../apigee/apigee-x-foundations/diagram4.png | Bin 0 -> 33815 bytes .../apigee/apigee-x-foundations/diagram5.png | Bin 0 -> 30537 bytes blueprints/apigee/apigee-x-foundations/dns.tf | 56 + .../functions/instance-monitor/index.js | 138 + .../instance-monitor/package-lock.json | 3271 +++++++++++++++++ .../functions/instance-monitor/package.json | 20 + blueprints/apigee/apigee-x-foundations/kms.tf | 64 + .../apigee/apigee-x-foundations/main.tf | 108 + .../apigee/apigee-x-foundations/monitoring.tf | 52 + .../apigee/apigee-x-foundations/northbound.tf | 248 ++ .../apigee/apigee-x-foundations/outputs.tf | 46 + .../apigee/apigee-x-foundations/variables.tf | 360 ++ modules/net-lb-app-int-cross-region/README.md | 2 +- 20 files changed, 4890 insertions(+), 3 deletions(-) create mode 100644 blueprints/apigee/apigee-x-foundations/README.md create mode 100644 blueprints/apigee/apigee-x-foundations/apigee.tf create mode 100644 blueprints/apigee/apigee-x-foundations/diagram1.png create mode 100644 blueprints/apigee/apigee-x-foundations/diagram2.png create mode 100644 blueprints/apigee/apigee-x-foundations/diagram3.png create mode 100644 blueprints/apigee/apigee-x-foundations/diagram4.png create mode 100644 blueprints/apigee/apigee-x-foundations/diagram5.png create mode 100644 blueprints/apigee/apigee-x-foundations/dns.tf create mode 100644 blueprints/apigee/apigee-x-foundations/functions/instance-monitor/index.js create mode 100644 blueprints/apigee/apigee-x-foundations/functions/instance-monitor/package-lock.json create mode 100644 blueprints/apigee/apigee-x-foundations/functions/instance-monitor/package.json create mode 100644 blueprints/apigee/apigee-x-foundations/kms.tf create mode 100644 blueprints/apigee/apigee-x-foundations/main.tf create mode 100644 blueprints/apigee/apigee-x-foundations/monitoring.tf create mode 100644 blueprints/apigee/apigee-x-foundations/northbound.tf create mode 100644 blueprints/apigee/apigee-x-foundations/outputs.tf create mode 100644 blueprints/apigee/apigee-x-foundations/variables.tf diff --git a/blueprints/README.md b/blueprints/README.md index ea91caa78b..3fbe689817 100644 --- a/blueprints/README.md +++ b/blueprints/README.md @@ -4,7 +4,7 @@ This section provides **[networking blueprints](./networking/)** that implement Currently available blueprints: -- **apigee** - [Apigee Hybrid on GKE](./apigee/hybrid-gke/), [Apigee X analytics in BigQuery](./apigee/bigquery-analytics), [Apigee network patterns](./apigee/network-patterns/) +- **apigee** - [Apigee X foundations](./apigee/apigee-x-foundations/). [Apigee Hybrid on GKE](./apigee/hybrid-gke/), [Apigee X analytics in BigQuery](./apigee/bigquery-analytics), [Apigee network patterns](./apigee/network-patterns/) - **cloud operations** - [Active Directory Federation Services](./cloud-operations/adfs), [Cloud Asset Inventory feeds for resource change tracking and remediation](./cloud-operations/asset-inventory-feed-remediation), [Fine-grained Cloud DNS IAM via Service Directory](./cloud-operations/dns-fine-grained-iam), [Cloud DNS & Shared VPC design](./cloud-operations/dns-shared-vpc), [Delegated Role Grants](./cloud-operations/iam-delegated-role-grants), [Network Quota Monitoring](./cloud-operations/network-quota-monitoring), [Managing on-prem service account keys by uploading public keys](./cloud-operations/onprem-sa-key-management), [Compute Image builder with Hashicorp Packer](./cloud-operations/packer-image-builder), [Packer example](./cloud-operations/packer-image-builder/packer), [Compute Engine quota monitoring](./cloud-operations/compute-quota-monitoring), [Scheduled Cloud Asset Inventory Export to Bigquery](./cloud-operations/scheduled-asset-inventory-export-bq), [Configuring workload identity federation with Terraform Cloud/Enterprise workflows](./cloud-operations/terraform-cloud-dynamic-credentials), [TCP healthcheck and restart for unmanaged GCE instances](./cloud-operations/unmanaged-instances-healthcheck), [Migrate for Compute Engine (v5) blueprints](./cloud-operations/vm-migration), [Configuring workload identity federation to access Google Cloud resources from apps running on Azure](./cloud-operations/workload-identity-federation) - **data solutions** - [GCE and GCS CMEK via centralized Cloud KMS](./data-solutions/cmek-via-centralized-kms), [Cloud Composer version 2 private instance, supporting Shared VPC and external CMEK key](./data-solutions/composer-2), [Cloud SQL instance with multi-region read replicas](./data-solutions/cloudsql-multiregion), [Data Platform](./data-solutions/data-platform-foundations), [Minimal Data Platform](./data-solutions/data-platform-minimal), [Spinning up a foundation data pipeline on Google Cloud using Cloud Storage, Dataflow and BigQuery](./data-solutions/gcs-to-bq-with-least-privileges), [#SQL Server Always On Groups blueprint](./data-solutions/sqlserver-alwayson), [Data Playground](./data-solutions/data-playground), [MLOps with Vertex AI](./data-solutions/vertex-mlops), [Shielded Folder](./data-solutions/shielded-folder), [BigQuery ML and Vertex AI Pipeline](./data-solutions/bq-ml) - **factories** - [Fabric resource factories](./factories) diff --git a/blueprints/apigee/README.md b/blueprints/apigee/README.md index 5f77147df3..6d0a9f5203 100644 --- a/blueprints/apigee/README.md +++ b/blueprints/apigee/README.md @@ -4,9 +4,15 @@ The blueprints in this folder contain a variety of deployment scenarios for Apig ## Blueprints +### Apigee X foundations + + This [blueprint](./apigee-x-foundations/) creates all the resources necessary to set up Apigee X on Google Cloud. + +
    + ### Apigee Hybrid on GKE - This [blueprint](./hybrid-gke/) shows how to do a non-prod deployment of Apigee Hybrid on GKE(../factories/net-vpc-firewall-yaml/). + This [blueprint](./hybrid-gke/) shows how to do a non-prod deployment of Apigee Hybrid on GKE.
    diff --git a/blueprints/apigee/apigee-x-foundations/README.md b/blueprints/apigee/apigee-x-foundations/README.md new file mode 100644 index 0000000000..3c336b8ed5 --- /dev/null +++ b/blueprints/apigee/apigee-x-foundations/README.md @@ -0,0 +1,481 @@ +# Apigee X Foundations + +This blueprint creates all the resources necessary to set up Apigee X on Google Cloud. + +Apigee can be exposed to clients using Regional Internal Application Load Balancer, Global External Application Load Balancer or both. When using the Regional Internal Application Load Balancer, used self-managed certificates (incuding self-signed certificates generated in this same module). When using the Global External Application Load Balancer Google-managed certificates or self-managed certificates (including self-signed certificates generated in this same module). When using Cross-region Internal Application Load Balancer a certificate manager needs to be used and it needs to be created in the same project as Apigee. + +Find below a few examples of different Apigee architectures that can be created using this module. + +## Examples + +* [Examples](#examples) + * [Apigee X in service project with shared VPC peered and exposed with Global External Application LB and Regional Internal Application LB](#apigee-x-in-service-project-with-shared-vpc-peered-and-exposed-with-global-external-application-lb-and-regional-internal-application-lb) + * [Apigee X in service project with local VPC peered and exposed using Global LB and Internal Cross-region Application LB](#apigee-x-in-service-project-with-local-vpc-peered-and-exposed-using-global-lb-and-internal-cross-region-application-lb) + * [Apigee X in service project with peering disabled and exposed using Global LB](#apigee-x-in-service-project-with-peering-disabled-and-exposed-using-global-lb) + * [Apigee X in standalone project with peering enabled and exposed with Regional Internal LB](#apigee-x-in-standalone-project-with-peering-enabled-and-exposed-with-regional-internal-lb) + * [Apigee X in standalone project with peering disabled and exposed using Global External Application LB](#apigee-x-in-standalone-project-with-peering-disabled-and-exposed-using-global-external-application-lb) +* [Variables](#files) +* [Variables](#variables) +* [Outputs](#outputs) + +### Apigee X in service project with shared VPC peered and exposed with Global External Application LB and Regional Internal Application LB + +![Diagram](./diagram1.png) + +```hcl +module "apigee-x-foundations" { + source = "./fabric/blueprints/apigee/apigee-x-foundations" + project_config = { + billing_account_id = var.billing_account_id + parent = var.folder_id + name = var.project_id + iam = { + "roles/apigee.admin" = ["group:apigee-admins@myorg.com"] + } + shared_vpc_service_config = { + host_project = "my-host-project" + } + } + apigee_config = { + addons_config = { + api_security = true + } + organization = { + analytics_region = "europe-west1" + } + envgroups = { + apis = [ + "apis.external.myorg.com", + "apis.internal.myorg.com" + ] + } + environments = { + apis = { + envgroups = ["apis"] + } + } + instances = { + europe-west1 = { + external = true + runtime_ip_cidr_range = "10.0.0.0/22" + troubleshooting_ip_cidr_range = "192.168.0.0/18" + environments = ["apis"] + } + } + endpoint_attachments = { + endpoint-backend-ew1 = { + region = "europe-west1" + service_attachment = "projects/a58971796302e0142p-tp/regions/europe-west4/serviceAttachments/my-service-attachment-ew1" + } + } + } + network_config = { + shared_vpc = { + name = "my-shared-vpc" + subnets = { + europe-west1 = "projects/my-host-project/regions/europe-west4/subnetworks/my-subnet-ew1" + } + subnets_psc = { + europe-west1 = "projects/my-host-project/regions/europe-west4/subnetworks/my-subnet-psc-ew1" + } + } + } + ext_lb_config = { + ssl_certificates = { + create_configs = { + default = { + certificate = "PEM-Encoded certificate string" + private_key = "PEM-Encoded private key string" + } + } + } + } + int_lb_config = { + ssl_certificates = { + create_configs = { + default = { + certificate = "PEM-Encoded certificate string" + private_key = "PEM-Encoded private key string" + } + } + } + } +} +# tftest modules=7 resources=42 +``` + +### Apigee X in service project with local VPC peered and exposed using Global LB and Internal Cross-region Application LB + +![Diagram](./diagram2.png) + +```hcl +module "apigee-x-foundations" { + source = "./fabric/blueprints/apigee/apigee-x-foundations" + project_config = { + billing_account_id = "1234-5678-0000" + parent = "folders/123456789" + name = "my-project" + iam = { + "roles/apigee.admin" = ["group:apigee-admins@myorg.com"] + } + shared_vpc_service_config = { + host_project = "my-host-project" + } + } + apigee_config = { + addons_config = { + api_security = true + } + organization = { + analytics_region = "europe-west1" + billing_type = "PAYG" + } + envgroups = { + apis = [ + "apis.external.myorg.com", + "apis.internal.myorg.com" + ] + } + environments = { + apis = { + envgroups = ["apis"] + type = "COMPREHENSIVE" + } + } + instances = { + europe-west1 = { + runtime_ip_cidr_range = "10.0.0.0/22" + troubleshooting_ip_cidr_range = "192.168.0.0/28" + environments = ["apis"] + } + europe-west4 = { + runtime_ip_cidr_range = "10.0.4.0/22" + troubleshooting_ip_cidr_range = "192.168.0.16/28" + environments = ["apis"] + } + } + endpoint_attachments = { + endpoint-backend-ew1 = { + region = "europe-west1" + service_attachment = "projects/a58971796302e0142p-tp/regions/europe-west1/serviceAttachments/my-service-attachment-ew1" + dns_names = [ + "backend.myorg.com" + ] + } + endpoint-backend-ew4 = { + region = "europe-west1" + service_attachment = "projects/a58971796302e0142p-tp/regions/europe-west4/serviceAttachments/my-service-attachment-ew4" + dns_names = [ + "backend.myorg.com" + ] + } + } + } + network_config = { + shared_vpc = { + name = "my-shared-vpc" + subnets = { + europe-west1 = "projects/my-host-project/regions/europe-west4/subnetworks/my-subnet-eu1" + europe-west4 = "projects/my-host-project/regions/europe-west4/subnetworks/my-subnet-eu4" + } + subnets_psc = { + europe-west1 = "projects/my-host-project/regions/europe-west4/subnetworks/my-subnet-psc-eu1" + europe-west4 = "projects/my-host-project/regions/europe-west4/subnetworks/my-subnet-psc-eu4" + } + } + apigee_vpc = { + auto_create = true + } + } + ext_lb_config = { + ssl_certificates = { + create_configs = { + default = { + certificate = "PEM-Encoded certificate string" + private_key = "PEM-Encoded private key string" + } + } + } + } + int_cross_region_lb_config = { + certificate_manager_certificates = [ + "projects/myprj/locations/global/certificates/certificate" + ] + } +} +# tftest modules=10 resources=62 +``` + +### Apigee X in service project with peering disabled and exposed using Global LB + +![Diagram](./diagram3.png) + +```hcl +module "apigee-x-foundations" { + source = "./fabric/blueprints/apigee/apigee-x-foundations" + project_config = { + billing_account_id = "1234-5678-0000" + parent = "folders/123456789" + name = "my-project" + iam = { + "roles/apigee.admin" = ["group:apigee-admins@myorg.com"] + } + shared_vpc_service_config = { + host_project = "my-host-project" + } + } + apigee_config = { + addons_config = { + api_security = true + } + organization = { + analytics_region = "europe-west1" + disable_vpc_peering = true + } + envgroups = { + apis = [ + "apis.external.myorg.com" + ] + } + environments = { + apis = { + envgroups = ["apis"] + } + } + instances = { + europe-west1 = { + runtime_ip_cidr_range = "10.0.0.0/22" + troubleshooting_ip_cidr_range = "192.168.0.0/18" + environments = ["apis"] + } + } + endpoint_attachments = { + endpoint-backend-ew1 = { + region = "europe-west1" + service_attachment = "projects/a58971796302e0142p-tp/regions/europe-west4/serviceAttachments/my-service-attachment-ew1" + } + } + disable_vpc_peering = true + } + network_config = { + shared_vpc = { + name = "my-shared-vpc" + subnets = { + europe-west1 = "projects/my-host-project/regions/europe-west4/subnetworks/my-subnet-ew1" + } + subnets_psc = { + europe-west1 = "projects/my-host-project/regions/europe-west4/subnetworks/my-subnet-psc-ew1" + } + } + } + ext_lb_config = { + ssl_certificates = { + create_configs = { + default = { + certificate = "PEM-Encoded certificate string" + private_key = "PEM-Encoded private key string" + } + } + } + } +} +# tftest modules=6 resources=36 +``` + +### Apigee X in standalone project with peering enabled and exposed with Regional Internal LB + +![Diagram](./diagram4.png) + +```hcl +module "apigee-x-foundations" { + source = "./fabric/blueprints/apigee/apigee-x-foundations" + project_config = { + billing_account_id = "1234-5678-0000" + parent = "folders/123456789" + name = "my-project" + iam = { + "roles/apigee.admin" = ["group:apigee-admins@myorg.com"] + } + } + apigee_config = { + addons_config = { + api_security = true + } + organization = { + analytics_region = "europe-west1" + } + envgroups = { + apis = [ + "apis.internal.myorg.com" + ] + } + environments = { + apis = { + envgroups = ["apis"] + } + } + instances = { + europe-west1 = { + runtime_ip_cidr_range = "172.16.0.0/22" + troubleshooting_ip_cidr_range = "192.168.0.0/18" + environments = ["apis"] + } + } + endpoint_attachments = { + endpoint-backend-ew1 = { + region = "europe-west1" + service_attachment = "projects/a58971796302e0142p-tp/regions/europe-west4/serviceAttachments/my-service-attachment-ew1" + dns_names = [ + "backend.myorg.com" + ] + } + } + } + network_config = { + apigee_vpc = { + subnets = { + europe-west1 = { + ip_cidr_range = "10.0.0.0/29" + } + } + subnets_proxy_only = { + europe-west1 = { + ip_cidr_range = "10.1.0.0/26" + } + } + subnets_psc = { + europe-west1 = { + ip_cidr_range = "10.0.1.0/29" + } + } + } + } + int_lb_config = { + ssl_certificates = { + create_configs = { + default = { + certificate = "PEM-Encoded certificate string" + private_key = "PEM-Encoded private key string" + } + } + } + } +} +# tftest modules=8 resources=48 +``` + +### Apigee X in standalone project with peering disabled and exposed using Global External Application LB + +![Diagram](./diagram5.png) + +```hcl +module "apigee-x-foundations" { + source = "./fabric/blueprints/apigee/apigee-x-foundations" + project_config = { + billing_account_id = "1234-5678-0000" + parent = "folders/123456789" + name = "my-project" + iam = { + "roles/apigee.admin" = ["group:apigee-admins@myorg.com"] + } + } + apigee_config = { + addons_config = { + api_security = true + } + organization = { + analytics_region = "europe-west1" + disable_vpc_peering = true + } + envgroups = { + apis = [ + "apis.external.myorg.com", + "apis.internal.myorg.com" + ] + } + environments = { + apis = { + envgroups = ["apis"] + } + } + instances = { + europe-west1 = { + environments = ["apis"] + } + } + endpoint_attachments = { + endpoint-backend-ew1 = { + region = "europe-west1" + service_attachment = "projects/a58971796302e0142p-tp/regions/europe-west4/serviceAttachments/my-service-attachment-ew1" + } + } + disable_vpc_peering = true + } + network_config = { + apigee_vpc = { + auto_create = true + subnets = { + europe-west1 = { + ip_cidr_range = "10.0.0.0/29" + } + } + subnets_psc = { + europe-west1 = { + ip_cidr_range = "10.0.1.0/29" + } + } + } + } + ext_lb_config = { + ssl_certificates = { + create_configs = { + default = { + certificate = "PEM-Encoded certificate string" + private_key = "PEM-Encoded private key string" + } + } + } + } + enable_monitoring = true +} +# tftest modules=8 resources=55 +``` + + + +## Files + +| name | description | modules | resources | +|---|---|---|---| +| [apigee.tf](./apigee.tf) | None | apigee | | +| [dns.tf](./dns.tf) | None | | | +| [kms.tf](./kms.tf) | None | kms | random_id | +| [main.tf](./main.tf) | Module-level locals and resources. | net-vpc · project | | +| [monitoring.tf](./monitoring.tf) | None | cloud-function-v2 | | +| [northbound.tf](./northbound.tf) | None | net-lb-app-ext · net-lb-app-int · net-lb-app-int-cross-region | google_compute_region_network_endpoint_group · google_compute_security_policy | +| [outputs.tf](./outputs.tf) | Module outputs. | | | +| [variables.tf](./variables.tf) | Module variables. | | | + +## Variables + +| name | description | type | required | default | producer | +|---|---|:---:|:---:|:---:|:---:| +| [apigee_config](variables.tf#L17) | Apigee configuration. | object({…}) | ✓ | | | +| [project_config](variables.tf#L271) | Project configuration. | object({…}) | ✓ | | | +| [enable_monitoring](variables.tf#L87) | Boolean flag indicating whether an custom metric to monitor instances should be created in Cloud monitoring. | bool | | false | | +| [ext_lb_config](variables.tf#L93) | External application load balancer configuration. | object({…}) | | null | | +| [int_cross_region_lb_config](variables.tf#L164) | Internal application load balancer configuration. | object({…}) | | null | | +| [int_lb_config](variables.tf#L192) | Internal application load balancer configuration. | object({…}) | | null | | +| [network_config](variables.tf#L228) | Network configuration. | object({…}) | | {} | | + +## Outputs + +| name | description | sensitive | consumers | +|---|---|:---:|---| +| [endpoint_attachment_hosts](outputs.tf#L17) | Endpoint attachment hosts. | | | +| [ext_lb_ip_address](outputs.tf#L22) | External IP address. | | | +| [instance_service_attachments](outputs.tf#L27) | Instance service attachments. | | | +| [int_cross_region_lb_ip_addresses](outputs.tf#L32) | Internal IP addresses. | | | +| [int_lb_ip_addresses](outputs.tf#L37) | Internal IP addresses. | | | +| [project_id](outputs.tf#L42) | Project. | | | + diff --git a/blueprints/apigee/apigee-x-foundations/apigee.tf b/blueprints/apigee/apigee-x-foundations/apigee.tf new file mode 100644 index 0000000000..86c738b859 --- /dev/null +++ b/blueprints/apigee/apigee-x-foundations/apigee.tf @@ -0,0 +1,37 @@ +/** + * Copyright 2023 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. + */ + +module "apigee" { + source = "../../../modules/apigee" + project_id = module.project.project_id + organization = merge(var.apigee_config.organization, var.network_config.apigee_vpc != null && !var.apigee_config.organization.disable_vpc_peering ? { + authorized_network = module.apigee_vpc[0].id + } : var.network_config.shared_vpc != null && !var.apigee_config.organization.disable_vpc_peering ? { + authorized_network = module.shared_vpc[0].id + } : {}, + var.apigee_config.organization.database_encryption_key == null ? {} : { + database_encryption_key = module.database_kms[0].keys["database-key"].id + }, { + runtime_type = "CLOUD" + }) + envgroups = var.apigee_config.envgroups + environments = var.apigee_config.environments + instances = { for k, v in var.apigee_config.instances : k => merge(v, v.disk_encryption_key == null ? { + disk_encryption_key = module.disks_kms[k].key_ids["disk-key"] + } : {}) } + endpoint_attachments = var.apigee_config.endpoint_attachments + addons_config = var.apigee_config.addons_config +} diff --git a/blueprints/apigee/apigee-x-foundations/diagram1.png b/blueprints/apigee/apigee-x-foundations/diagram1.png new file mode 100644 index 0000000000000000000000000000000000000000..e8b33532323d4b7cf3cdf9b73be24648ad56284c GIT binary patch literal 37153 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 Xz_sq(O+FWpd7nI{bu{ycdBFbxPVQDZ literal 0 HcmV?d00001 diff --git a/blueprints/apigee/apigee-x-foundations/diagram2.png b/blueprints/apigee/apigee-x-foundations/diagram2.png new file mode 100644 index 0000000000000000000000000000000000000000..94f4b1af57c608eca925d6e0a7a15d084bb2949c GIT binary patch literal 52665 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

    DlDDcV3JR_QlM3${*B;!_=0A?7rAc2{j3Zr_g$8 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 literal 0 HcmV?d00001 diff --git a/blueprints/apigee/apigee-x-foundations/dns.tf b/blueprints/apigee/apigee-x-foundations/dns.tf new file mode 100644 index 0000000000..20b0170856 --- /dev/null +++ b/blueprints/apigee/apigee-x-foundations/dns.tf @@ -0,0 +1,56 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +locals { + dns_names = [for v1 in flatten([for k2, v2 in var.apigee_config.endpoint_attachments : + [for v3 in coalesce(v2.dns_names, []) : { + endpoint_attachment = k2 + dns_name = split(".", v3) + } + if v3 != null]]) : { + endpoint_attachment = v1.endpoint_attachment + domain = length(v1.dns_name) == 1 ? "." : "${join(".", slice(v1.dns_name, 1, length(v1.dns_name)))}." + name = length(v1.dns_name) == 1 ? "*." : v1.dns_name[0] + }] + peered_domains = distinct([for v in local.dns_names : v.domain]) + private_dns_zones = { for k1, v1 in { for v2 in local.peered_domains : + v2 => distinct([for v3 in local.dns_names : v3.name if v3.domain == v2]) } : k1 => + { for v4 in v1 : "A ${v4}" => { + geo_routing = [for v5 in local.dns_names : + { + location = var.apigee_config.endpoint_attachments[v5.endpoint_attachment].region + records = [module.apigee.endpoint_attachment_hosts[v5.endpoint_attachment]] + } + if v5.domain == k1 && v5.name == v4] } + } + } +} + +module "private_dns_zones" { + for_each = (var.network_config.apigee_vpc == null || var.apigee_config.organization.disable_vpc_peering + ? {} : + local.private_dns_zones) + source = "../../../modules/dns" + project_id = module.project.project_id + name = trimsuffix(replace(each.key, ".", "-"), "-") + zone_config = { + domain = each.key + private = { + client_networks = [module.apigee_vpc[0].self_link] + } + } + recordsets = each.value +} diff --git a/blueprints/apigee/apigee-x-foundations/functions/instance-monitor/index.js b/blueprints/apigee/apigee-x-foundations/functions/instance-monitor/index.js new file mode 100644 index 0000000000..56dc61fed6 --- /dev/null +++ b/blueprints/apigee/apigee-x-foundations/functions/instance-monitor/index.js @@ -0,0 +1,138 @@ +/** + * Copyright 2024 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. + */ + +const functions = require("@google-cloud/functions-framework"); +const monitoring = require("@google-cloud/monitoring"); +const logging = require("@google-cloud/logging"); +const { LoggingBunyan } = require("@google-cloud/logging-bunyan"); +const bunyan = require("bunyan"); + +const loggingBunyan = new LoggingBunyan(); +const logger = bunyan.createLogger({ + name: "instance-monitor", + streams: [{ stream: process.stdout, level: "info" }, loggingBunyan.stream("info")], +}); + +const SEVERITY_THRESHOLD = logging.Severity.warning; + +const METRIC_DESCRIPTION = "Apigee instance health."; +const METRIC_DISPLAY_NAME = "Apigee instance health."; +const METRIC_TYPE = "custom.googleapis.com/apigee/instance_health"; +const METRIC_KIND = "GAUGE"; +const METRIC_VALUE_TYPE = "BOOL"; +const METRIC_UNIT = "1"; +const METRIC_LABELS = [ + { + key: "org", + valueType: "STRING", + description: "The name of the apigee organization.", + }, + { + key: "instance_id", + valueType: "STRING", + description: "The ID of the apigee instance.", + } +]; +const RESOURCE_TYPE = "global"; +const METRIC_DESCRIPTOR = { + description: METRIC_DESCRIPTION, + displayName: METRIC_DISPLAY_NAME, + type: METRIC_TYPE, + metricKind: METRIC_KIND, + valueType: METRIC_VALUE_TYPE, + unit: METRIC_UNIT, + labels: METRIC_LABELS, +}; + +const client = new monitoring.MetricServiceClient(); + +async function createMetricDescriptor(projectId, metricDescriptor) { + const request = { + name: client.projectPath(projectId), + metricDescriptor: metricDescriptor, + }; + return await client.createMetricDescriptor(request); +} + +async function getMetricDescriptor(projectId, metricType) { + const request = { + name: client.projectMetricDescriptorPath(projectId, metricType), + }; + return await client.getMetricDescriptor(request); +} + +async function writeTimeSeriesData(projectId, metricType, resourceType, value, metricLabels) { + const dataPoint = { + interval: { + endTime: { + seconds: Date.now() / 1000, + }, + }, + value: { + boolValue: value, + }, + }; + + const timeSeriesData = { + metric: { + type: metricType, + labels: metricLabels, + }, + resource: { + type: resourceType, + labels: { + project_id: projectId, + }, + }, + points: [dataPoint], + }; + + const request = { + name: client.projectPath(projectId), + timeSeries: [timeSeriesData], + }; + + return await client.createTimeSeries(request); +} + +async function processEvent(cloudEvent) { + const [, projectId, instanceId] = /^organizations\/(.+)\/instances\/(.+)$/g.exec(cloudEvent.resourcename); + const severity = logging.Severity[cloudEvent.data.severity.toLowerCase()]; + const value = severity >= SEVERITY_THRESHOLD; + if (!value) { + logger.error(`Instance ${instanceId} in ${organization} is down`); + } + try { + logger.debug("Checking if metric exists..."); + const result = await getMetricDescriptor(projectId, METRIC_TYPE); + logger.debug("Metric already exists", result); + } catch (error) { + logger.debug("Metric does not exist. Creating it..."); + const result = await createMetricDescriptor(projectId, METRIC_DESCRIPTOR); + logger.debug("Metric created", result); + } + logger.debug("Writing data point..."); + await writeTimeSeriesData(projectId, METRIC_TYPE, RESOURCE_TYPE, value, { + org: projectId, + instance_id: instanceId, + }); +} + +functions.cloudEvent("writeMetric", async cloudEvent => { + logger.debug("Notification received. Let's process it..."); + processEvent(cloudEvent); + logger.debug("Notification processed."); +}); diff --git a/blueprints/apigee/apigee-x-foundations/functions/instance-monitor/package-lock.json b/blueprints/apigee/apigee-x-foundations/functions/instance-monitor/package-lock.json new file mode 100644 index 0000000000..7fff997d3e --- /dev/null +++ b/blueprints/apigee/apigee-x-foundations/functions/instance-monitor/package-lock.json @@ -0,0 +1,3271 @@ +{ + "name": "instance-checker", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "instance-checker", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@google-cloud/functions-framework": "^3.3.0", + "@google-cloud/logging": "^10.5.0", + "@google-cloud/logging-bunyan": "^5.0.0", + "@google-cloud/monitoring": "^3.0.5", + "bunyan": "^1.8.15" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "dependencies": { + "@babel/highlight": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", + "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@google-cloud/common": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-4.0.3.tgz", + "integrity": "sha512-fUoMo5b8iAKbrYpneIRV3z95AlxVJPrjpevxs4SKoclngWZvTXBSGpNisF5+x5m+oNGve7jfB1e6vNBZBUs7Fw==", + "dependencies": { + "@google-cloud/projectify": "^3.0.0", + "@google-cloud/promisify": "^3.0.0", + "arrify": "^2.0.1", + "duplexify": "^4.1.1", + "ent": "^2.2.0", + "extend": "^3.0.2", + "google-auth-library": "^8.0.2", + "retry-request": "^5.0.0", + "teeny-request": "^8.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@google-cloud/functions-framework": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@google-cloud/functions-framework/-/functions-framework-3.3.0.tgz", + "integrity": "sha512-+4O1dX5VNRK1W1NyAia7zy5jLf88ytuz39/1kVUUaNiOf76YbMZKV0YjZwfk7uEwRrC6l2wynK1G+q8Gb5DeVw==", + "dependencies": { + "@types/express": "4.17.17", + "body-parser": "^1.18.3", + "cloudevents": "^7.0.0", + "express": "^4.16.4", + "minimist": "^1.2.7", + "on-finished": "^2.3.0", + "read-pkg-up": "^7.0.1", + "semver": "^7.3.5" + }, + "bin": { + "functions-framework": "build/src/main.js", + "functions-framework-nodejs": "build/src/main.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@google-cloud/logging": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@google-cloud/logging/-/logging-10.5.0.tgz", + "integrity": "sha512-XmlNs6B8lDZvFwFB5M55g9ch873AA2rXSuFOczQ3FOAzuyd/qksf18suFJfcrLMu8lYSr3SQhTE45FlXz4p9pg==", + "dependencies": { + "@google-cloud/common": "^4.0.0", + "@google-cloud/paginator": "^4.0.0", + "@google-cloud/projectify": "^3.0.0", + "@google-cloud/promisify": "^3.0.0", + "arrify": "^2.0.1", + "dot-prop": "^6.0.0", + "eventid": "^2.0.0", + "extend": "^3.0.2", + "gcp-metadata": "^4.0.0", + "google-auth-library": "^8.0.2", + "google-gax": "^3.5.8", + "on-finished": "^2.3.0", + "pumpify": "^2.0.1", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@google-cloud/logging-bunyan": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/logging-bunyan/-/logging-bunyan-5.0.0.tgz", + "integrity": "sha512-Ila0PB6JZ/Qv18wx3x4/NoPLoSfTTDCvEIeaxCyKouv97kEWOP+HYvwimdJCYlvePyClZH+y3ktW17XlJAE4gA==", + "dependencies": { + "@google-cloud/logging": "^10.2.2", + "google-auth-library": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "bunyan": "*" + } + }, + "node_modules/@google-cloud/logging-bunyan/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@google-cloud/logging-bunyan/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@google-cloud/logging-bunyan/node_modules/gaxios": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.1.1.tgz", + "integrity": "sha512-bw8smrX+XlAoo9o1JAksBwX+hi/RG15J+NTSxmNPIclKC3ZVK6C2afwY8OSdRvOK0+ZLecUJYtj2MmjOt3Dm0w==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/logging-bunyan/node_modules/gcp-metadata": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.0.0.tgz", + "integrity": "sha512-Ozxyi23/1Ar51wjUT2RDklK+3HxqDr8TLBNK8rBBFQ7T85iIGnXnVusauj06QyqCXRFZig8LZC+TUddWbndlpQ==", + "dependencies": { + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/logging-bunyan/node_modules/google-auth-library": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.1.0.tgz", + "integrity": "sha512-1M9HdOcQNPV5BwSXqwwT238MTKodJFBxZ/V2JP397ieOLv4FjQdfYb9SooR7Mb+oUT2IJ92mLJQf804dyx0MJA==", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.0.0", + "gcp-metadata": "^6.0.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/logging-bunyan/node_modules/gtoken": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.0.1.tgz", + "integrity": "sha512-KcFVtoP1CVFtQu0aSk3AyAt2og66PFhZAlkUOuWKwzMLoulHXG5W5wE5xAnHb+yl3/wEFoqGW7/cDGMU8igDZQ==", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/logging-bunyan/node_modules/https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@google-cloud/logging-bunyan/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/@google-cloud/logging/node_modules/gaxios": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", + "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", + "dependencies": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.7" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/logging/node_modules/gcp-metadata": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", + "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", + "dependencies": { + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/logging/node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@google-cloud/monitoring": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@google-cloud/monitoring/-/monitoring-3.0.5.tgz", + "integrity": "sha512-txZPpmE+HGkx1wo5SO975Y7M1lhnIUMI79+7SDTHvVU7lNvQB6i6rNqOSkJZYHj5I/JFFqHIarUtSoIWU0ZCSw==", + "dependencies": { + "google-gax": "^3.5.8" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@google-cloud/paginator": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-4.0.1.tgz", + "integrity": "sha512-6G1ui6bWhNyHjmbYwavdN7mpVPRBtyDg/bfqBTAlwr413On2TnFNfDxc9UhTJctkgoCDgQXEKiRPLPR9USlkbQ==", + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-3.0.0.tgz", + "integrity": "sha512-HRkZsNmjScY6Li8/kb70wjGlDDyLkVk3KvoEo9uIoxSjYLJasGiCch9+PqRVDOCGUFvEIqyogl+BeqILL4OJHA==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz", + "integrity": "sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.8.19", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.19.tgz", + "integrity": "sha512-yCkvhtstJvUL3DEQAF5Uq5KoqQL27MTdfxVYvGsGduoGxiJcRCvJ29t5OmLfLhRuRpjAQ1OlhJD/IPOfX+8jNw==", + "dependencies": { + "@grpc/proto-loader": "^0.7.0", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.8.tgz", + "integrity": "sha512-GU12e2c8dmdXb7XUlOgYWZ2o2i+z9/VeACkxTA/zzAe2IjclC5PnVL0lpgjhrqfpDYHzM8B1TF6pqWegMYAzlA==", + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^7.2.4", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@jsdoc/salty": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.5.tgz", + "integrity": "sha512-TfRP53RqunNe2HBobVBJ0VLhK1HbfvBYeTC1ahnN64PWvyYyGebmMiPkuwvD9fpw2ZbkoPb8Q7mwy0aR8Z9rvw==", + "dependencies": { + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v12.0.0" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", + "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.35", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", + "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", + "dependencies": { + "@types/minimatch": "^5.1.2", + "@types/node": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==" + }, + "node_modules/@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==" + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, + "node_modules/@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "dependencies": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==" + }, + "node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" + }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==" + }, + "node_modules/@types/node": { + "version": "20.4.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.4.tgz", + "integrity": "sha512-CukZhumInROvLq3+b5gLev+vgpsIqC2D0deQr/yS1WnxvmYLlJXZpaQrQiseMY+6xusl79E04UjWoqyr+t1/Ew==" + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==" + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, + "node_modules/@types/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==", + "dependencies": { + "@types/glob": "*", + "@types/node": "*" + } + }, + "node_modules/@types/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", + "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", + "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==", + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "engines": { + "node": ">=8" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bignumber.js": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "engines": { + "node": "*" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/bunyan": { + "version": "1.8.15", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.15.tgz", + "integrity": "sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==", + "engines": [ + "node >=0.10.0" + ], + "bin": { + "bunyan": "bin/bunyan" + }, + "optionalDependencies": { + "dtrace-provider": "~0.8", + "moment": "^2.19.3", + "mv": "~2", + "safe-json-stringify": "~1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cloudevents": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cloudevents/-/cloudevents-7.0.2.tgz", + "integrity": "sha512-WiOqWsNkMZmMMZ6xa3kzx/MA+8+V+c5eGkStZIcik+Px2xCobmzcacw1EOGyfhODaQKkIv8TxXOOLzV69oXFqA==", + "dependencies": { + "ajv": "^8.11.0", + "ajv-formats": "^2.1.1", + "json-bigint": "^1.0.0", + "process": "^0.11.10", + "util": "^0.12.4", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=16 <=20" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dtrace-provider": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", + "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "nan": "^2.14.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/duplexify": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", + "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==" + }, + "node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/eventid/-/eventid-2.0.1.tgz", + "integrity": "sha512-sPNTqiMokAvV048P2c9+foqVJzk49o6d4e0D/sq5jog3pw+4kBgyR0gaM1FM7Mx6Kzd9dztesh9oYz1LWWOpzw==", + "dependencies": { + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/express/node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, + "node_modules/fast-text-encoding": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==" + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/gaxios": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", + "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/gcp-metadata": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", + "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/google-auth-library": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.9.0.tgz", + "integrity": "sha512-f7aQCJODJFmYWN6PeNKzgvy9LI2tYmXnzpNDHEjG5sDNPgGb2FXQyTBnXeSH+PAtpKESFD+LmHw3Ox3mN7e1Fg==", + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.3.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/google-gax": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.6.1.tgz", + "integrity": "sha512-g/lcUjGcB6DSw2HxgEmCDOrI/CByOwqRvsuUvNalHUK2iPPPlmAIpbMbl62u0YufGMr8zgE3JL7th6dCb1Ry+w==", + "dependencies": { + "@grpc/grpc-js": "~1.8.0", + "@grpc/proto-loader": "^0.7.0", + "@types/long": "^4.0.0", + "@types/rimraf": "^3.0.2", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "fast-text-encoding": "^1.0.3", + "google-auth-library": "^8.0.2", + "is-stream-ended": "^0.1.4", + "node-fetch": "^2.6.1", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^1.0.0", + "protobufjs": "7.2.4", + "protobufjs-cli": "1.1.1", + "retry-request": "^5.0.0" + }, + "bin": { + "compileProtos": "build/tools/compileProtos.js", + "minifyProtoJson": "build/tools/minify.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/google-p12-pem": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", + "dependencies": { + "node-forge": "^1.3.1" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/gtoken": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "dependencies": { + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==" + }, + "node_modules/is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dependencies": { + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, + "node_modules/jsdoc": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.2.tgz", + "integrity": "sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==", + "dependencies": { + "@babel/parser": "^7.20.15", + "@jsdoc/salty": "^0.2.1", + "@types/markdown-it": "^12.2.3", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "underscore": "~1.13.2" + }, + "bin": { + "jsdoc": "jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsdoc/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dependencies": { + "graceful-fs": "^4.1.9" + } + }, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/mv": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", + "integrity": "sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg==", + "optional": true, + "dependencies": { + "mkdirp": "~0.5.1", + "ncp": "~2.0.0", + "rimraf": "~2.4.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/mv/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/mv/node_modules/glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==", + "optional": true, + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mv/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mv/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "optional": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mv/node_modules/rimraf": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", + "integrity": "sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==", + "optional": true, + "dependencies": { + "glob": "^6.0.1" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/nan": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", + "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", + "optional": true + }, + "node_modules/ncp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==", + "optional": true, + "bin": { + "ncp": "bin/ncp" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/proto3-json-serializer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.1.tgz", + "integrity": "sha512-AwAuY4g9nxx0u52DnSMkqqgyLHaW/XaPLtaAo3y/ZCfeaQB/g4YDH4kb8Wc/mWzWvu0YjOznVnfn373MVZZrgw==", + "dependencies": { + "protobufjs": "^7.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/protobufjs": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz", + "integrity": "sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/protobufjs-cli": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.1.tgz", + "integrity": "sha512-VPWMgIcRNyQwWUv8OLPyGQ/0lQY/QTQAVN5fh+XzfDwsVw1FZ2L3DM/bcBf8WPiRz2tNpaov9lPZfNcmNo6LXA==", + "dependencies": { + "chalk": "^4.0.0", + "escodegen": "^1.13.0", + "espree": "^9.0.0", + "estraverse": "^5.1.0", + "glob": "^8.0.0", + "jsdoc": "^4.0.0", + "minimist": "^1.2.0", + "semver": "^7.1.2", + "tmp": "^0.2.1", + "uglify-js": "^3.7.7" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "protobufjs": "^7.0.0" + } + }, + "node_modules/protobufjs-cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/protobufjs-cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/protobufjs-cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/protobufjs-cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/protobufjs-cli/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/protobufjs-cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/protobufjs/node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", + "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", + "dependencies": { + "duplexify": "^4.1.1", + "inherits": "^2.0.3", + "pump": "^3.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/retry-request": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.2.tgz", + "integrity": "sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ==", + "dependencies": { + "debug": "^4.1.1", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/retry-request/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/retry-request/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-json-stringify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", + "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", + "optional": true + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", + "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==" + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==" + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/teeny-request": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.3.tgz", + "integrity": "sha512-jJZpA5He2y52yUhA7pyAGZlgQpcB+xLjcN0eUFxr9c8hP/H7uOXbBNVo/O0C/xVfJLJs680jvkFgVJEEvk9+ww==", + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/teeny-request/node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", + "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/blueprints/apigee/apigee-x-foundations/functions/instance-monitor/package.json b/blueprints/apigee/apigee-x-foundations/functions/instance-monitor/package.json new file mode 100644 index 0000000000..c1004d3ccc --- /dev/null +++ b/blueprints/apigee/apigee-x-foundations/functions/instance-monitor/package.json @@ -0,0 +1,20 @@ +{ + "name": "instance-checker", + "version": "1.0.0", + "description": "", + "main": "index.js", + "engines": { + "node": ">=18.0.0" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@google-cloud/functions-framework": "^3.3.0", + "@google-cloud/logging-bunyan": "^5.0.0", + "bunyan": "^1.8.15", + "@google-cloud/monitoring": "^3.0.5" + } +} diff --git a/blueprints/apigee/apigee-x-foundations/kms.tf b/blueprints/apigee/apigee-x-foundations/kms.tf new file mode 100644 index 0000000000..120a118ca3 --- /dev/null +++ b/blueprints/apigee/apigee-x-foundations/kms.tf @@ -0,0 +1,64 @@ +/** + * Copyright 2024 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 "random_id" "database_kms" { + byte_length = 4 +} + +resource "random_id" "disks_kms" { + for_each = var.apigee_config.instances + byte_length = 4 +} + +module "database_kms" { + count = try(var.apigee_config.organization.database_encryption_key, null) == null ? 1 : 0 + source = "../../../modules/kms" + project_id = module.project.project_id + keyring = { + location = "global" + name = "apigee-${random_id.database_kms.hex}" + } + keys = { + database-key = { + purpose = "ENCRYPT_DECRYPT" + rotation_period = "2592000s" + labels = null + iam = { + "roles/cloudkms.cryptoKeyEncrypterDecrypter" = ["serviceAccount:${module.project.service_accounts.robots.apigee}"] + } + } + } +} + +module "disks_kms" { + for_each = var.apigee_config.instances + source = "../../../modules/kms" + project_id = module.project.project_id + keyring = { + location = each.key + name = "apigee-${each.key}-${random_id.disks_kms[each.key].hex}" + } + keys = { + disk-key = { + purpose = "ENCRYPT_DECRYPT" + rotation_period = "2592000s" + labels = null + iam = { + "roles/cloudkms.cryptoKeyEncrypterDecrypter" = ["serviceAccount:${module.project.service_accounts.robots.apigee}"] + } + } + } +} diff --git a/blueprints/apigee/apigee-x-foundations/main.tf b/blueprints/apigee/apigee-x-foundations/main.tf new file mode 100644 index 0000000000..d25c1c11b3 --- /dev/null +++ b/blueprints/apigee/apigee-x-foundations/main.tf @@ -0,0 +1,108 @@ +/** + * Copyright 2024 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. + */ + +module "project" { + source = "../../../modules/project" + billing_account = var.project_config.billing_account_id + compute_metadata = var.project_config.compute_metadata + custom_roles = var.project_config.custom_roles + default_service_account = var.project_config.default_service_account + iam = var.project_config.iam + iam_bindings = var.project_config.iam_bindings + iam_bindings_additive = var.project_config.iam_bindings_additive + labels = var.project_config.labels + lien_reason = var.project_config.lien_reason + logging_data_access = var.project_config.logging_data_access + logging_exclusions = var.project_config.log_exclusions + logging_sinks = var.project_config.logging_sinks + metric_scopes = var.project_config.metric_scopes + name = var.project_config.name + org_policies = var.project_config.org_policies + parent = var.project_config.parent + prefix = var.project_config.prefix + services = distinct(concat(var.project_config.services, [ + "apigee.googleapis.com", + "cloudkms.googleapis.com", + "compute.googleapis.com", + "eventarc.googleapis.com", + "dns.googleapis.com", + "iam.googleapis.com", + "servicenetworking.googleapis.com", + ], var.enable_monitoring ? [ + "cloudbuild.googleapis.com", + "cloudfunctions.googleapis.com", + "logging.googleapis.com", + "monitoring.googleapis.com", + "pubsub.googleapis.com", + "run.googleapis.com" + ] : [])) + + shared_vpc_service_config = var.project_config.shared_vpc_service_config + skip_delete = var.project_config.skip_delete + tag_bindings = var.project_config.tag_bindings +} + +module "shared_vpc" { + count = var.network_config.shared_vpc == null ? 0 : 1 + source = "../../../modules/net-vpc" + project_id = var.project_config.shared_vpc_service_config.host_project + name = var.network_config.shared_vpc.name + vpc_create = false +} + +module "apigee_vpc" { + count = var.network_config.apigee_vpc == null ? 0 : 1 + source = "../../../modules/net-vpc" + project_id = module.project.project_id + name = coalesce(var.network_config.apigee_vpc.name, "apigee-vpc") + vpc_create = var.network_config.apigee_vpc.auto_create + psa_configs = [{ + ranges = merge(flatten([for k, v in var.apigee_config.instances : merge( + v.runtime_ip_cidr_range == null ? {} : { "apigee-22-${k}" = v.runtime_ip_cidr_range }, + v.troubleshooting_ip_cidr_range == null ? {} : { "apigee-28-${k}" = v.troubleshooting_ip_cidr_range } + )])...) + export_routes = true + import_routes = false + peered_domains = local.peered_domains + }] + subnets = [for k, v in var.network_config.apigee_vpc.subnets : + { + name = coalesce(v.name, "subnet-${k}") + region = k + ip_cidr_range = v.ip_cidr_range + description = "Subnet in ${k} region" + } + if v.ip_cidr_range != null && (var.int_cross_region_lb_config != null || nonsensitive(var.int_lb_config != null))] + subnets_proxy_only = [for k, v in var.network_config.apigee_vpc.subnets_proxy_only : + { + name = coalesce(v.name, "subnet-proxy-only-${k}") + region = k + ip_cidr_range = v.ip_cidr_range + description = "Proxy-only subnet in ${k} region" + global = var.int_cross_region_lb_config != null + } + if v.ip_cidr_range != null && (var.int_cross_region_lb_config != null || nonsensitive(var.int_lb_config != null))] + subnets_psc = [for k, v in var.network_config.apigee_vpc.subnets_psc : + { + name = coalesce(v.name, "subnet-psc-${k}") + region = k + ip_cidr_range = v.ip_cidr_range + description = "PSC Subnet in ${k} region" + global = var.int_cross_region_lb_config != null + } + if v.ip_cidr_range != null] +} + diff --git a/blueprints/apigee/apigee-x-foundations/monitoring.tf b/blueprints/apigee/apigee-x-foundations/monitoring.tf new file mode 100644 index 0000000000..70d3a2195d --- /dev/null +++ b/blueprints/apigee/apigee-x-foundations/monitoring.tf @@ -0,0 +1,52 @@ +/** + * Copyright 2024 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. + */ + +module "instance_monitor_function" { + count = var.enable_monitoring && length(var.apigee_config.instances) > 0 ? 1 : 0 + source = "../../../modules/cloud-function-v2" + project_id = module.project.project_id + name = "instance-monitor" + bucket_name = module.project.project_id + bucket_config = { + } + bundle_config = { + source_dir = "${path.module}/functions/instance-monitor" + output_path = "bundle.zip" + } + function_config = { + entry_point = "writeMetric" + runtime = "nodejs20" + timeout = 180 + } + trigger_config = { + event_type = "google.cloud.audit.log.v1.written" + region = "global" + event_filters = [ + { + attribute = "serviceName" + value = "apigee.googleapis.com" + }, + { + attribute = "methodName" + value = "google.cloud.apigee.v1.RuntimeService.ReportInstanceStatus" + }, + ] + service_account_create = true + retry_policy = "RETRY_POLICY_DO_NOT_RETRY" + } + region = var.apigee_config.organization.analytics_region + service_account_create = true +} diff --git a/blueprints/apigee/apigee-x-foundations/northbound.tf b/blueprints/apigee/apigee-x-foundations/northbound.tf new file mode 100644 index 0000000000..caae13bf07 --- /dev/null +++ b/blueprints/apigee/apigee-x-foundations/northbound.tf @@ -0,0 +1,248 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +locals { + preconfigured_waf_rules = { for k, v in try(var.ext_lb_config.security_policy.preconfigured_waf_rules, {}) : k => + merge(v.sensitivity == null ? {} : { + sensitivity = v.sensitivity + }, + length(v.opt_in_rule_ids) > 0 ? { + opt_in_rule_ids = v.opt_in_rule_ids + } : {}, + length(v.opt_out_rule_ids) > 0 ? { + opt_out_rule_ids = v.opt_out_rule_ids + } : {}) + } + network = try(module.shared_vpc[0].id, module.apigee_vpc[0].id) + neg_subnets = (var.network_config.shared_vpc == null ? + (try(var.network_config.apigee_vpc.auto_create, false) ? + { for k, v in module.apigee_vpc[0].subnets_psc : v.region => v.id } : + { for k, v in var.network_config.apigee_vpc.subnets_psc : v => v.id }) : + var.network_config.shared_vpc.subnets_psc + ) + ilb_subnets = (var.network_config.shared_vpc == null ? + (try(var.network_config.apigee_vpc.auto_create, false) ? + { for k, v in module.apigee_vpc[0].subnets : v.region => v.id } : + { for k, v in var.network_config.apigee_vpc.subnets : v => v.id }) : + var.network_config.shared_vpc.subnets + ) + ext_instances = var.ext_lb_config == null ? {} : { for k, v in local.neg_subnets : k => module.apigee.instances[k] } + int_instances = var.int_lb_config == null ? {} : { for k, v in local.ilb_subnets : k => module.apigee.instances[k] } + int_cross_region_instances = var.int_cross_region_lb_config == null ? {} : { for k, v in local.ilb_subnets : k => module.apigee.instances[k] } +} + +resource "google_compute_region_network_endpoint_group" "psc_negs" { + for_each = local.neg_subnets + project = module.project.project_id + region = each.key + name = "apigee-${each.key}" + network_endpoint_type = "PRIVATE_SERVICE_CONNECT" + psc_target_service = module.apigee.instances[each.key].service_attachment + network = local.network + subnetwork = each.value +} + +module "ext_lb" { + count = length(local.ext_instances) > 0 ? 1 : 0 + source = "../../../modules/net-lb-app-ext" + name = "ext-lb" + project_id = module.project.project_id + protocol = "HTTPS" + use_classic_version = false + backend_service_configs = { + default = { + backends = [for k, v in local.ext_instances : { backend = google_compute_region_network_endpoint_group.psc_negs[k].id }] + protocol = "HTTPS" + health_checks = [] + outlier_detection = var.ext_lb_config.outlier_detection + security_policy = try(google_compute_security_policy.policy[0].name, null) + log_sample_rate = var.ext_lb_config.log_sample_rate + } + } + health_check_configs = { + default = { + https = { port_specification = "USE_SERVING_PORT" } + } + } + ssl_certificates = var.ext_lb_config.ssl_certificates +} + +module "int_lb" { + for_each = local.int_instances + source = "../../../modules/net-lb-app-int" + name = "${each.key}-int-lb" + project_id = module.project.project_id + region = each.key + protocol = "HTTPS" + backend_service_configs = { + default = { + backends = [{ + group = google_compute_region_network_endpoint_group.psc_negs[each.key].id + }] + outlier_detection = var.int_lb_config.outlier_detection + health_checks = [] + log_sample_rate = var.int_lb_config.log_sample_rate + } + } + ssl_certificates = var.int_lb_config.ssl_certificates + vpc_config = { + network = local.network + subnetwork = local.ilb_subnets[each.key] + } +} + +module "int_cross_region_lb" { + count = length(local.int_cross_region_instances) > 0 ? 1 : 0 + source = "../../../modules/net-lb-app-int-cross-region" + name = "int-cross-region-lb" + project_id = module.project.project_id + protocol = "HTTPS" + backend_service_configs = { + default = { + backends = [for k, v in google_compute_region_network_endpoint_group.psc_negs : { + group = v.id + }] + outlier_detection = var.int_cross_region_lb_config.outlier_detection + health_checks = [] + log_sample_rate = var.int_cross_region_lb_config.log_sample_rate + } + } + https_proxy_config = { + certificate_manager_certificates = var.int_cross_region_lb_config.certificate_manager_certificates + } + vpc_config = { + network = local.network + subnetworks = local.ilb_subnets + } +} + +resource "google_compute_security_policy" "policy" { + provider = google-beta + count = try(var.ext_lb_config.security_policy, null) == null ? 0 : 1 + name = "cloud-armor-security-policy" + description = "Cloud Armor Security Policy" + project = module.project.project_id + dynamic "advanced_options_config" { + for_each = try(var.ext_lb_config, null) == null ? [] : [""] + content { + json_parsing = try(var.ext_lb_config.security_policy.adaptive_protection_config.json_parsing.enable, false) ? "DISABLED" : "STANDARD" + dynamic "json_custom_config" { + for_each = try(var.ext_lb_config.security_policy.adaptive_protection_config.json_parsing.content_types, null) == null ? [] : [""] + content { + content_types = var.ext_lb_config.security_policy.adaptive_protection_config.json_parsing.content_types + } + } + log_level = var.ext_lb_config.security_policy.advanced_options_config.log_level + } + } + dynamic "adaptive_protection_config" { + for_each = try(var.ext_lb_config.security_policy.adaptive_protection_config, null) == null ? [] : [""] + content { + dynamic "layer_7_ddos_defense_config" { + for_each = try(var.ext_lb_config.security_policy.adaptive_protection_config.layer_7_ddos_defense_config, null) == null ? [] : [""] + content { + enable = var.ext_lb_config.security_policy.adaptive_protection_config.layer_7_ddos_defense_config.enable + rule_visibility = var.ext_lb_config.security_policy.adaptive_protection_config.layer_7_ddos_defense_config.rule_visibility + } + } + dynamic "auto_deploy_config" { + for_each = try(var.int_lb_config.security_policy.adaptive_protection_config.auto_deploy_config, null) == null ? [] : [""] + content { + load_threshold = var.ext_lb_config.security_policy.adaptive_protection_config.auto_deploy_config.load_threshold + confidence_threshold = var.ext_lb_config.security_policy.adaptive_protection_config.auto_deploy_config.confidence_threshold + impacted_baseline_threshold = var.ext_lb_config.security_policy.adaptive_protection_config.auto_deploy_config.impacted_baseline_threshold + expiration_sec = var.ext_lb_config.security_policy.adaptive_protection_config.auto_deploy_config.expiration_sec + } + } + } + } + type = "CLOUD_ARMOR" + dynamic "rule" { + for_each = try(var.ext_lb_config.security_policy.rate_limit_threshold, null) == null ? [] : [""] + content { + action = "throttle" + priority = 3000 + rate_limit_options { + enforce_on_key = "ALL" + conform_action = "allow" + exceed_action = "deny(429)" + rate_limit_threshold { + count = var.ext_lb_config.security_policy.rate_limit_threshold.count + interval_sec = var.ext_lb_config.security_policy.rate_limit_threshold.interval_sec + } + } + match { + versioned_expr = "SRC_IPS_V1" + config { + src_ip_ranges = ["*"] + } + } + description = "Rate limit all user IPs" + } + } + dynamic "rule" { + for_each = try(length(var.ext_lb_config.security_policy.forbidden_src_ip_ranges), 0) > 0 ? [""] : [] + content { + action = "deny(403)" + priority = 5000 + match { + versioned_expr = "SRC_IPS_V1" + config { + src_ip_ranges = var.ext_lb_config.security_policy.forbidden_src_ip_ranges + } + } + description = "Deny access to IPs in specific ranges" + } + } + dynamic "rule" { + for_each = try(length(var.ext_lb_config.security_policy.forbidden_regions), 0) > 0 ? [""] : [] + content { + action = "deny(403)" + priority = 7000 + match { + expr { + expression = "origin.region_code.matches(\"^${join("|", var.ext_lb_config.security_policy.forbidden_regions)}$\")" + } + } + description = "Block users from forbidden regions" + } + } + dynamic "rule" { + for_each = local.preconfigured_waf_rules + content { + action = "deny(403)" + priority = 10000 + index(keys(var.ext_lb_config.security_policy.preconfigured_waf_rules), rule.key) * 1000 + match { + expr { + expression = "evaluatePreconfiguredWaf(\"${rule.key}\"${length(rule.value) > 0 ? join("", [",", jsonencode(rule.value)]) : ""})" + } + } + description = "Preconfigured WAF rule (${rule.key})" + } + } + rule { + action = "allow" + priority = 2147483647 + match { + versioned_expr = "SRC_IPS_V1" + config { + src_ip_ranges = ["*"] + } + } + description = "default rule" + } +} diff --git a/blueprints/apigee/apigee-x-foundations/outputs.tf b/blueprints/apigee/apigee-x-foundations/outputs.tf new file mode 100644 index 0000000000..3ce7b959c7 --- /dev/null +++ b/blueprints/apigee/apigee-x-foundations/outputs.tf @@ -0,0 +1,46 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +output "endpoint_attachment_hosts" { + description = "Endpoint attachment hosts." + value = module.apigee.endpoint_attachment_hosts +} + +output "ext_lb_ip_address" { + description = "External IP address." + value = var.ext_lb_config != null && length(local.ext_instances) > 0 ? module.ext_lb[0].address : null +} + +output "instance_service_attachments" { + description = "Instance service attachments." + value = { for k, v in module.apigee.instances : k => v.service_attachment } +} + +output "int_cross_region_lb_ip_addresses" { + description = "Internal IP addresses." + value = var.int_cross_region_lb_config != null && length(local.int_cross_region_instances) > 0 ? module.int_cross_region_lb[0].addresses : null +} + +output "int_lb_ip_addresses" { + description = "Internal IP addresses." + value = var.int_lb_config != null && length(local.int_instances) > 0 ? { for k, v in module.int_lb : k => v.address } : null +} + +output "project_id" { + description = "Project." + value = module.project.project_id +} + diff --git a/blueprints/apigee/apigee-x-foundations/variables.tf b/blueprints/apigee/apigee-x-foundations/variables.tf new file mode 100644 index 0000000000..a874de822a --- /dev/null +++ b/blueprints/apigee/apigee-x-foundations/variables.tf @@ -0,0 +1,360 @@ +/** + * Copyright 2023 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. + */ + +variable "apigee_config" { + description = "Apigee configuration." + type = object({ + addons_config = optional(object({ + advanced_api_ops = optional(bool, false) + api_security = optional(bool, false) + connectors_platform = optional(bool, false) + integration = optional(bool, false) + monetization = optional(bool, false) + })) + organization = object({ + display_name = optional(string) + description = optional(string, "Terraform-managed") + billing_type = optional(string) + database_encryption_key = optional(string) + analytics_region = optional(string, "europe-west1") + retention = optional(string) + disable_vpc_peering = optional(bool, false) + }) + envgroups = optional(map(list(string)), {}) + environments = optional(map(object({ + description = optional(string) + display_name = optional(string) + envgroups = optional(list(string), []) + iam = optional(map(list(string)), {}) + iam_bindings = optional(map(object({ + role = string + members = list(string) + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) + })), {}) + iam_bindings_additive = optional(map(object({ + role = string + member = string + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) + })), {}) + node_config = optional(object({ + min_node_count = optional(number) + max_node_count = optional(number) + }), {}) + type = optional(string) + })), {}) + instances = optional(map(object({ + disk_encryption_key = optional(string) + environments = optional(list(string), []) + external = optional(bool, true) + runtime_ip_cidr_range = optional(string) + troubleshooting_ip_cidr_range = optional(string) + })), {}) + endpoint_attachments = optional(map(object({ + region = string + service_attachment = string + dns_names = optional(list(string), []) + })), {}) + }) + validation { + condition = (!var.apigee_config.organization.disable_vpc_peering || + alltrue([for k, v in var.apigee_config.endpoint_attachments : length(v.dns_names) == 0])) + error_message = "If disable_vpc_peering is true for the organization, DNS names cannot be used for endpoint attachments." + } + nullable = false +} + +variable "enable_monitoring" { + description = "Boolean flag indicating whether an custom metric to monitor instances should be created in Cloud monitoring." + type = bool + default = false +} + +variable "ext_lb_config" { + description = "External application load balancer configuration." + type = object({ + log_sample_rate = optional(number) + outlier_detection = optional(object({ + consecutive_errors = optional(number) + consecutive_gateway_failure = optional(number) + enforcing_consecutive_errors = optional(number) + enforcing_consecutive_gateway_failure = optional(number) + enforcing_success_rate = optional(number) + max_ejection_percent = optional(number) + success_rate_minimum_hosts = optional(number) + success_rate_request_volume = optional(number) + success_rate_stdev_factor = optional(number) + base_ejection_time = optional(object({ + seconds = number + nanos = optional(number) + })) + interval = optional(object({ + seconds = number + nanos = optional(number) + })) + })) + security_policy = optional(object({ + advanced_options_config = optional(object({ + json_parsing = optional(object({ + enable = optional(bool, false) + content_types = optional(list(string)) + })) + log_level = optional(string) + })) + adaptive_protection_config = optional(object({ + layer_7_ddos_defense_config = optional(object({ + enable = optional(bool, false) + rule_visibility = optional(string) + })) + auto_deploy_config = optional(object({ + load_threshold = optional(number) + confidence_threshold = optional(number) + impacted_baseline_threshold = optional(number) + expiration_sec = optional(number) + })) + })) + rate_limit_threshold = optional(object({ + count = number + interval_sec = number + })) + forbidden_src_ip_ranges = optional(list(string), []) + forbidden_regions = optional(list(string), []) + preconfigured_waf_rules = optional(map(object({ + sensitivity = optional(number) + opt_in_rule_ids = optional(list(string), []) + opt_out_rule_ids = optional(list(string), []) + }))) + })) + ssl_certificates = object({ + certificate_ids = optional(list(string), []) + create_configs = optional(map(object({ + certificate = string + private_key = string + })), {}) + managed_configs = optional(map(object({ + domains = list(string) + description = optional(string) + })), {}) + self_signed_configs = optional(list(string), null) + }) + }) + default = null +} + +variable "int_cross_region_lb_config" { + description = "Internal application load balancer configuration." + type = object({ + log_sample_rate = optional(number) + outlier_detection = optional(object({ + consecutive_errors = optional(number) + consecutive_gateway_failure = optional(number) + enforcing_consecutive_errors = optional(number) + enforcing_consecutive_gateway_failure = optional(number) + enforcing_success_rate = optional(number) + max_ejection_percent = optional(number) + success_rate_minimum_hosts = optional(number) + success_rate_request_volume = optional(number) + success_rate_stdev_factor = optional(number) + base_ejection_time = optional(object({ + seconds = number + nanos = optional(number) + })) + interval = optional(object({ + seconds = number + nanos = optional(number) + })) + })) + certificate_manager_certificates = optional(list(string)) + }) + default = null +} + +variable "int_lb_config" { + description = "Internal application load balancer configuration." + type = object({ + log_sample_rate = optional(number) + outlier_detection = optional(object({ + consecutive_errors = optional(number) + consecutive_gateway_failure = optional(number) + enforcing_consecutive_errors = optional(number) + enforcing_consecutive_gateway_failure = optional(number) + enforcing_success_rate = optional(number) + max_ejection_percent = optional(number) + success_rate_minimum_hosts = optional(number) + success_rate_request_volume = optional(number) + success_rate_stdev_factor = optional(number) + base_ejection_time = optional(object({ + seconds = number + nanos = optional(number) + })) + interval = optional(object({ + seconds = number + nanos = optional(number) + })) + })) + ssl_certificates = object({ + certificate_ids = optional(list(string), []) + create_configs = optional(map(object({ + certificate = string + private_key = string + })), {}) + self_signed_configs = optional(list(string), []) + }) + }) + default = null +} + + +variable "network_config" { + description = "Network configuration." + type = object({ + shared_vpc = optional(object({ + name = string + subnets = map(string) + subnets_psc = map(string) + })) + apigee_vpc = optional(object({ + name = optional(string) + auto_create = optional(bool, true) + subnets = optional(map(object({ + id = optional(string) + name = optional(string) + ip_cidr_range = optional(string) + })), {}) + subnets_proxy_only = optional(map(object({ + name = optional(string) + ip_cidr_range = string + })), {}) + subnets_psc = optional(map(object({ + id = optional(string) + name = optional(string) + ip_cidr_range = optional(string) + })), {}) + })) + }) + nullable = false + default = {} + validation { + condition = var.network_config.shared_vpc != null || var.network_config.apigee_vpc != null + error_message = "Shared VPC and/or local VPC details need to be provided." + } + validation { + condition = alltrue([for k, v in try(var.network_config.apigee_vpc.subnets, {}) : (v.id != null || v.ip_cidr_range != null) && !(v.id != null && v.ip_cidr_range != null)]) + error_message = "An IP CIDR range and id cannot be specified at the same time for a subnet." + } + validation { + condition = alltrue([for k, v in try(var.network_config.apigee_vpc.subnets_psc, {}) : (v.id != null || v.ip_cidr_range != null) && !(v.id != null && v.ip_cidr_range != null)]) + error_message = "An IP CIDR range and id cannot be specified at the same time for a PSC subnet." + } +} + +variable "project_config" { + description = "Project configuration." + type = object({ + billing_account_id = optional(string) + compute_metadata = optional(map(string), {}) + contacts = optional(map(list(string)), {}) + custom_roles = optional(map(list(string)), {}) + default_service_account = optional(string, "keep") + descriptive_name = optional(string) + iam = optional(map(list(string)), {}) + group_iam = optional(map(list(string)), {}) + iam_bindings = optional(map(object({ + role = string + members = list(string) + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) + })), {}) + iam_bindings_additive = optional(map(object({ + role = string + member = string + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) + })), {}) + labels = optional(map(string), {}) + lien_reason = optional(string) + logging_data_access = optional(map(map(list(string))), {}) + log_exclusions = optional(map(string), {}) + logging_sinks = optional(map(object({ + bq_partitioned_table = optional(bool) + description = optional(string) + destination = string + disabled = optional(bool, false) + exclusions = optional(map(string), {}) + filter = string + iam = optional(bool, true) + type = string + unique_writer = optional(bool, true) + })), {}) + metric_scopes = optional(list(string), []) + name = string + org_policies = optional(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) + }), {}) + })), []) + })), {}) + parent = optional(string) + prefix = optional(string) + project_create = optional(bool, true) + vpc_sc = optional(object({ + perimeter_name = string + perimeter_bridges = optional(list(string), []) + is_dry_run = optional(bool, false) + })) + services = optional(list(string), []) + shared_vpc_host_config = optional(object({ + enabled = bool + service_projects = optional(list(string), []) + })) + shared_vpc_service_config = optional(object({ + host_project = string + service_identity_iam = optional(map(list(string)), {}) + service_iam_grants = optional(list(string), []) + })) + skip_delete = optional(bool, false) + tag_bindings = optional(map(string)) + }) +} + diff --git a/modules/net-lb-app-int-cross-region/README.md b/modules/net-lb-app-int-cross-region/README.md index 4bbaf4cc8c..4e6155c4ff 100644 --- a/modules/net-lb-app-int-cross-region/README.md +++ b/modules/net-lb-app-int-cross-region/README.md @@ -1,4 +1,4 @@ -# Internal Application Load Balancer Module +# Cross-region Internal Application Load Balancer Module This module allows managing Cross-regional Internal HTTP/HTTPS Load Balancers (L7 ILBs). It's designed to expose the full configuration of the underlying resources, and to facilitate common usage patterns by providing sensible defaults, and optionally managing prerequisite resources like health checks, instance groups, etc. From 95d0cccff4f9ae933eab0610492c478d5a3730e1 Mon Sep 17 00:00:00 2001 From: Ludo Date: Tue, 14 May 2024 16:54:51 +0200 Subject: [PATCH 31/31] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a34721182..0f76361713 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file. ### BLUEPRINTS +- [[#2274](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2274)] Added apigee-x-foundations blueprint ([apichick](https://github.com/apichick)) - [[#2243](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2243)] Added new attributes Apigee organization and bumped up providers version ([apichick](https://github.com/apichick)) - [[#2239](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2239)] Update README.md ([vicenteg](https://github.com/vicenteg)) - [[#2230](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2230)] docs: :memo: fix error in phpIPAM terraform config by updating VPC pe… ([PapaPeskwo](https://github.com/PapaPeskwo)) @@ -55,6 +56,7 @@ All notable changes to this project will be documented in this file. ### MODULES +- [[#2274](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2274)] Added apigee-x-foundations blueprint ([apichick](https://github.com/apichick)) - [[#2270](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2270)] Cloud function CMEK key support ([luigi-bitonti](https://github.com/luigi-bitonti)) - [[#2272](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2272)] New Bindplane cloud-config-container setup ([simonebruzzechesse](https://github.com/simonebruzzechesse)) - [[#2269](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/2269)] Implement the full IAM interface for tags ([ludoo](https://github.com/ludoo))

    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 V92K}5pdT65R{U5JU_~-xt literal 0 HcmV?d00001 diff --git a/blueprints/apigee/apigee-x-foundations/diagram4.png b/blueprints/apigee/apigee-x-foundations/diagram4.png new file mode 100644 index 0000000000000000000000000000000000000000..613b762441005eac0aaad8ae69675c3a0d0a9252 GIT binary patch literal 33815 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 E2dS&=LjV8( literal 0 HcmV?d00001 diff --git a/blueprints/apigee/apigee-x-foundations/diagram5.png b/blueprints/apigee/apigee-x-foundations/diagram5.png new file mode 100644 index 0000000000000000000000000000000000000000..402a759bbe867906f0f0991108f351e8f71d4f88 GIT binary patch literal 30537 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