From f6c0ebcdc57916a333bf539b9af7231167d5bc5a Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Mon, 18 Jul 2022 14:43:53 +0000 Subject: [PATCH 01/41] Wordpress example: work started --- examples/third-party-solutions/wordpress/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 examples/third-party-solutions/wordpress/README.md diff --git a/examples/third-party-solutions/wordpress/README.md b/examples/third-party-solutions/wordpress/README.md new file mode 100644 index 00000000..46409041 --- /dev/null +++ b/examples/third-party-solutions/wordpress/README.md @@ -0,0 +1 @@ +# TODO From fba679ab494d70ac2c9a1f0aafe84d7341d97b47 Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Fri, 22 Jul 2022 08:22:56 +0000 Subject: [PATCH 02/41] some boilerplate code added --- .../third-party-solutions/wordpress/main.tf | 65 +++++++++++++++++++ .../wordpress/variables.tf | 46 +++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 examples/third-party-solutions/wordpress/main.tf create mode 100644 examples/third-party-solutions/wordpress/variables.tf diff --git a/examples/third-party-solutions/wordpress/main.tf b/examples/third-party-solutions/wordpress/main.tf new file mode 100644 index 00000000..e0fd3ea0 --- /dev/null +++ b/examples/third-party-solutions/wordpress/main.tf @@ -0,0 +1,65 @@ +/** + * 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 { + all_principals_iam = [ + for k in var.principals : + "user:${k}" + ] + iam = { + # GCS roles + "roles/storage.objectAdmin" = [ + module.service-account-gcs.iam_email, + ] + # CloudSQL + "roles/cloudsql.admin" = local.all_principals_iam + "roles/cloudsql.client" = concat( + local.all_principals_iam, + [module.service-account-sql.iam_email] + ) + "roles/cloudsql.instanceUser" = concat( + local.all_principals_iam, + [module.service-account-sql.iam_email] + ) + # common roles + "roles/logging.admin" = local.all_principals_iam + "roles/iam.serviceAccountUser" = local.all_principals_iam + "roles/iam.serviceAccountTokenCreator" = local.all_principals_iam + } +} + +module "project" { + source = "../../../modules/project" + name = var.project_id + parent = try(var.project_create.parent, null) + billing_account = try(var.project_create.billing_account_id, null) + project_create = var.project_create != null + prefix = var.project_create == null ? null : var.prefix + iam = var.project_create != null ? local.iam : {} + iam_additive = var.project_create == null ? local.iam : {} + services = [ + "run.googleapis.com", + "logging.googleapis.com", + "monitoring.googleapis.com", + "sqladmin.googleapis.com", + "sql-component.googleapis.com", + "storage.googleapis.com", + "storage-component.googleapis.com", + ] + service_config = { + disable_on_destroy = false, disable_dependent_services = false + } +} \ No newline at end of file diff --git a/examples/third-party-solutions/wordpress/variables.tf b/examples/third-party-solutions/wordpress/variables.tf new file mode 100644 index 00000000..ec8cc8bc --- /dev/null +++ b/examples/third-party-solutions/wordpress/variables.tf @@ -0,0 +1,46 @@ +/** + * 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 "prefix" { + description = "Unique prefix used for resource names. Not used for project if 'project_create' is null." + type = string +} + +variable "project_create" { + description = "Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format." + type = object({ + billing_account_id = string + parent = string + }) + default = null +} + +variable "project_id" { + description = "Project id, references existing project if `project_create` is null." + type = string +} + +variable "region" { + type = string + description = "Region for the created resources" + default = "europe-west4" +} + +variable "principals" { + description = "List of emails of people/service accounts to give rights to, eg 'user@domain.com'." + type = list(string) + default = [] +} \ No newline at end of file From bcb4a334a0fe06b39831a3c60d501a9fc2a6ca7c Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Tue, 9 Aug 2022 09:21:24 +0000 Subject: [PATCH 03/41] using Fabric modules, CloudSQL internal IP - first approximation --- .../wordpress/cloudrun/README.md | 1 + .../wordpress/cloudrun/main.tf | 329 ++++++++++++++++++ .../wordpress/{ => cloudrun}/variables.tf | 12 + .../third-party-solutions/wordpress/main.tf | 65 ---- 4 files changed, 342 insertions(+), 65 deletions(-) create mode 100644 examples/third-party-solutions/wordpress/cloudrun/README.md create mode 100644 examples/third-party-solutions/wordpress/cloudrun/main.tf rename examples/third-party-solutions/wordpress/{ => cloudrun}/variables.tf (76%) delete mode 100644 examples/third-party-solutions/wordpress/main.tf diff --git a/examples/third-party-solutions/wordpress/cloudrun/README.md b/examples/third-party-solutions/wordpress/cloudrun/README.md new file mode 100644 index 00000000..46409041 --- /dev/null +++ b/examples/third-party-solutions/wordpress/cloudrun/README.md @@ -0,0 +1 @@ +# TODO diff --git a/examples/third-party-solutions/wordpress/cloudrun/main.tf b/examples/third-party-solutions/wordpress/cloudrun/main.tf new file mode 100644 index 00000000..67a5a32d --- /dev/null +++ b/examples/third-party-solutions/wordpress/cloudrun/main.tf @@ -0,0 +1,329 @@ +/** + * 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 { + all_principals_iam = [ + for k in var.principals : + "user:${k}" + ] + iam = { + # CloudSQL + "roles/cloudsql.admin" = local.all_principals_iam + "roles/cloudsql.client" = concat( + local.all_principals_iam, + #[module.service-account-sql.iam_email] + ) + "roles/cloudsql.instanceUser" = concat( + local.all_principals_iam, + #[module.service-account-sql.iam_email] + ) + # common roles + "roles/logging.admin" = local.all_principals_iam + "roles/iam.serviceAccountUser" = local.all_principals_iam + "roles/iam.serviceAccountTokenCreator" = local.all_principals_iam + } + cloud_sql_conf = { + availability_type = "REGIONAL" + database_version = "MYSQL_8_0" + psa_range = "10.60.0.0/16" + tier = "db-g1-small" + db = "wp-mysql" + user = "admin" + pass = "password" + } + sql_vpc_cidr = "10.0.0.0/20" +} + +module "project" { + source = "../../../../modules/project" + name = var.project_id + parent = try(var.project_create.parent, null) + billing_account = try(var.project_create.billing_account_id, null) + project_create = var.project_create != null + prefix = var.project_create == null ? null : var.prefix + iam = var.project_create != null ? local.iam : {} + iam_additive = var.project_create == null ? local.iam : {} + services = [ + "run.googleapis.com", + "logging.googleapis.com", + "monitoring.googleapis.com", + "sqladmin.googleapis.com", + "sql-component.googleapis.com", + "storage.googleapis.com", + "storage-component.googleapis.com", + "vpcaccess.googleapis.com" + ] + service_config = { + disable_on_destroy = false, disable_dependent_services = false + } +} + +module "cloud_run" { + source = "../../../../modules/cloud-run" + project_id = module.project.project_id + name = "${var.prefix}-cr-wordpress" + region = var.region + cloudsql_instances = module.cloudsql.connection_name + vpc_connector = { + create = true + name = "wp-connector" + egress_settings = "all-traffic" + } + vpc_connector_config = { + network = module.vpc.self_link + ip_cidr_range = "10.8.0.0/28" # !!! + } +# "run.googleapis.com/ingress" = "internal-and-cloud-load-balancing" + + containers = [{ + image = var.wordpress_image + ports = [{ # TODO https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_service + name = "http1" + protocol = "TCP" + container_port = 80 + }] + options = { + command = null + args = null + env_from = null + env = { + "DB_HOST" : module.cloudsql.ip #google_sql_database_instance.cloud_sql.private_ip_address + "DB_USER" : local.cloud_sql_conf.user + "DB_PASSWORD" : local.cloud_sql_conf.pass + "DB_NAME" : local.cloud_sql_conf.db + "WORDPRESS_DEBUG": "1" + } + } + resources = null + volume_mounts = null + }] + + iam = { + "roles/run.invoker": [var.cloud_run_invoker] + } +} + + +# tftest modules=1 resources=1 + +/* +resource "google_cloud_run_service" "cloud_run" { + project = module.project.project_id + name = "${var.prefix}-cr-wordpress" + location = var.region + + template { + spec { + containers { + image = var.wordpress_image + ports { + container_port = 80 + } + env { + name = "DB_HOST" + value = "34.91.36.235" + } + env { + name = "DB_USER" + value = "admin" + } + env { + name = "DB_PASSWORD" + value = "password" + } + env { + name = "DB_NAME" + value = "wp-mysql" + } + env { + name = "WORDPRESS_DEBUG" + value = "1" + } + } + } + + metadata { + annotations = { + #"autoscaling.knative.dev/maxScale" = "10" + "run.googleapis.com/cloudsql-instances" = module.cloudsql.connection_name + "run.googleapis.com/client-name" = "terraform" + } + } + } +} + +# Grant Cloud Run usage rights to someone who is authorized to access the end-point +resource "google_cloud_run_service_iam_member" "cloud_run_iam_member" { + project = module.project.project_id + location = google_cloud_run_service.cloud_run.location + service = google_cloud_run_service.cloud_run.name + + role = "roles/run.invoker" + + member = var.cloud_run_invoker +} +*/ + +module "vpc" { + source = "../../../../modules/net-vpc" + project_id = module.project.project_id + name = "vpc" + subnets = [ + { + ip_cidr_range = local.sql_vpc_cidr + name = "subnet" + region = var.region + secondary_ip_range = {} + } + ] + + psa_config = { + ranges = { cloud-sql = local.cloud_sql_conf.psa_range } + routes = null + } +} + +module "firewall" { + source = "../../../../modules/net-vpc-firewall" + project_id = module.project.project_id + network = module.vpc.name + admin_ranges = [local.sql_vpc_cidr] +} + +module "nat" { + source = "../../../../modules/net-cloudnat" + project_id = module.project.project_id + region = var.region + name = "${var.prefix}-default" + router_network = module.vpc.name +} + +module "cloudsql" { + source = "../../../../modules/cloudsql-instance" + project_id = module.project.project_id + availability_type = local.cloud_sql_conf.availability_type + network = module.vpc.self_link + # network = data.google_compute_network.default.id + name = "${var.prefix}-mysql" + region = var.region + database_version = local.cloud_sql_conf.database_version + tier = local.cloud_sql_conf.tier + databases = [local.cloud_sql_conf.db] + users = { + "${local.cloud_sql_conf.user}" = "${local.cloud_sql_conf.pass}" + } +# backup_configuration = { +# enabled = true +# binary_log_enabled = true +# start_time = "23:00" +# location = var.region +# log_retention_days = 7 +# retention_count = 7 +# } +# authorized_networks = { +# internet = "0.0.0.0/0" +# } +} + +#data "google_compute_network" "default" { +# name = "default" +#} + +/* +resource "google_compute_global_address" "private_ip_address" { + name = "private-ip-address" + purpose = "VPC_PEERING" + address_type = "INTERNAL" + prefix_length = 16 + network = module.vpc.self_link + #network = data.google_compute_network.default.id +} + +resource "google_service_networking_connection" "private_vpc_connection" { + network = module.vpc.self_link + #network = data.google_compute_network.default.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.private_ip_address.name] +} +*/ + +# ------------------------------------------------------------ +/* +resource "google_compute_global_address" "default" { + name = "${var.wordpress_project_name}-address" +} + +# #OPTIONAL LB START + +#or use your own cert here +#certificate can take up to 24h to provision +resource "google_compute_managed_ssl_certificate" "default" { + name = "${var.wordpress_project_name}-cert" + managed { + domains = ["nip.io"]#tovars + } +} + + +resource "google_compute_region_network_endpoint_group" "cloudrun_neg" { + name = "${var.wordpress_project_name}-neg" + network_endpoint_type = "SERVERLESS" + region = var.region + cloud_run { + service = google_cloud_run_service.cloud_run.name + } +} + +module "lb-http" { + source = "GoogleCloudPlatform/lb-http/google//modules/serverless_negs" + project = var.project + + name = "${var.wordpress_project_name}-lb" + + managed_ssl_certificate_domains = ["nip.io"] + ssl = true + https_redirect = true + + backends = { + default = { + groups = [ + { + group = google_compute_region_network_endpoint_group.cloudrun_neg.id + } + ] + + enable_cdn = false + + log_config = { + enable = false + sample_rate = 0.0 + } + + iap_config = { + enable = false + oauth2_client_id = null + oauth2_client_secret = null + } + + description = null + custom_request_headers = null + custom_response_headers = null + security_policy = null + } + } +} +#END OPTIONAL LB +*/ diff --git a/examples/third-party-solutions/wordpress/variables.tf b/examples/third-party-solutions/wordpress/cloudrun/variables.tf similarity index 76% rename from examples/third-party-solutions/wordpress/variables.tf rename to examples/third-party-solutions/wordpress/cloudrun/variables.tf index ec8cc8bc..415d0444 100644 --- a/examples/third-party-solutions/wordpress/variables.tf +++ b/examples/third-party-solutions/wordpress/cloudrun/variables.tf @@ -43,4 +43,16 @@ variable "principals" { description = "List of emails of people/service accounts to give rights to, eg 'user@domain.com'." type = list(string) default = [] +} + +variable "wordpress_image" { + type = string + description = "Image to run with Cloud Run, starts with \"gcr.io\"" +} + +# Documentation: https://cloud.google.com/run/docs/securing/managing-access#making_a_service_public +variable "cloud_run_invoker" { + type = string + description = "IAM member authorized to access the end-point (for example, 'user:YOUR_IAM_USER' for only you or 'allUsers' for everyone)" + default = "allUsers" } \ No newline at end of file diff --git a/examples/third-party-solutions/wordpress/main.tf b/examples/third-party-solutions/wordpress/main.tf deleted file mode 100644 index e0fd3ea0..00000000 --- a/examples/third-party-solutions/wordpress/main.tf +++ /dev/null @@ -1,65 +0,0 @@ -/** - * 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 { - all_principals_iam = [ - for k in var.principals : - "user:${k}" - ] - iam = { - # GCS roles - "roles/storage.objectAdmin" = [ - module.service-account-gcs.iam_email, - ] - # CloudSQL - "roles/cloudsql.admin" = local.all_principals_iam - "roles/cloudsql.client" = concat( - local.all_principals_iam, - [module.service-account-sql.iam_email] - ) - "roles/cloudsql.instanceUser" = concat( - local.all_principals_iam, - [module.service-account-sql.iam_email] - ) - # common roles - "roles/logging.admin" = local.all_principals_iam - "roles/iam.serviceAccountUser" = local.all_principals_iam - "roles/iam.serviceAccountTokenCreator" = local.all_principals_iam - } -} - -module "project" { - source = "../../../modules/project" - name = var.project_id - parent = try(var.project_create.parent, null) - billing_account = try(var.project_create.billing_account_id, null) - project_create = var.project_create != null - prefix = var.project_create == null ? null : var.prefix - iam = var.project_create != null ? local.iam : {} - iam_additive = var.project_create == null ? local.iam : {} - services = [ - "run.googleapis.com", - "logging.googleapis.com", - "monitoring.googleapis.com", - "sqladmin.googleapis.com", - "sql-component.googleapis.com", - "storage.googleapis.com", - "storage-component.googleapis.com", - ] - service_config = { - disable_on_destroy = false, disable_dependent_services = false - } -} \ No newline at end of file From cc60ae850e4c62b900f0ce1c38beb329437ec54e Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Fri, 19 Aug 2022 12:24:58 +0000 Subject: [PATCH 04/41] next iteration of code refining (WiP) --- .../wordpress/cloudrun/main.tf | 93 ++++--------------- .../wordpress/cloudrun/outputs.tf | 15 +++ .../cloudrun/terraform.tfvars.sample | 2 + .../wordpress/cloudrun/variables.tf | 3 +- 4 files changed, 38 insertions(+), 75 deletions(-) create mode 100644 examples/third-party-solutions/wordpress/cloudrun/outputs.tf create mode 100644 examples/third-party-solutions/wordpress/cloudrun/terraform.tfvars.sample diff --git a/examples/third-party-solutions/wordpress/cloudrun/main.tf b/examples/third-party-solutions/wordpress/cloudrun/main.tf index 67a5a32d..09756920 100644 --- a/examples/third-party-solutions/wordpress/cloudrun/main.tf +++ b/examples/third-party-solutions/wordpress/cloudrun/main.tf @@ -15,6 +15,7 @@ */ locals { + prefix = var.prefix == null ? "" : "${var.prefix}-" all_principals_iam = [ for k in var.principals : "user:${k}" @@ -36,15 +37,16 @@ locals { "roles/iam.serviceAccountTokenCreator" = local.all_principals_iam } cloud_sql_conf = { - availability_type = "REGIONAL" + availability_type = "ZONAL" database_version = "MYSQL_8_0" psa_range = "10.60.0.0/16" tier = "db-g1-small" db = "wp-mysql" user = "admin" - pass = "password" + pass = "password" # TODO: var } sql_vpc_cidr = "10.0.0.0/20" + connector_cidr = "10.8.0.0/28" # !!! } module "project" { @@ -62,29 +64,30 @@ module "project" { "monitoring.googleapis.com", "sqladmin.googleapis.com", "sql-component.googleapis.com", - "storage.googleapis.com", - "storage-component.googleapis.com", + #"storage.googleapis.com", + #"storage-component.googleapis.com", "vpcaccess.googleapis.com" ] - service_config = { - disable_on_destroy = false, disable_dependent_services = false - } +# service_config = { +# disable_on_destroy = false, +# disable_dependent_services = false +# } } module "cloud_run" { source = "../../../../modules/cloud-run" project_id = module.project.project_id - name = "${var.prefix}-cr-wordpress" + name = "${local.prefix}cr-wordpress" region = var.region cloudsql_instances = module.cloudsql.connection_name vpc_connector = { create = true - name = "wp-connector" + name = "${local.prefix}wp-connector" egress_settings = "all-traffic" } vpc_connector_config = { network = module.vpc.self_link - ip_cidr_range = "10.8.0.0/28" # !!! + ip_cidr_range = local.connector_cidr } # "run.googleapis.com/ingress" = "internal-and-cloud-load-balancing" @@ -100,11 +103,11 @@ module "cloud_run" { args = null env_from = null env = { - "DB_HOST" : module.cloudsql.ip #google_sql_database_instance.cloud_sql.private_ip_address + "DB_HOST" : module.cloudsql.ip "DB_USER" : local.cloud_sql_conf.user "DB_PASSWORD" : local.cloud_sql_conf.pass "DB_NAME" : local.cloud_sql_conf.db - "WORDPRESS_DEBUG": "1" + # "WORDPRESS_DEBUG": "1" } } resources = null @@ -120,51 +123,6 @@ module "cloud_run" { # tftest modules=1 resources=1 /* -resource "google_cloud_run_service" "cloud_run" { - project = module.project.project_id - name = "${var.prefix}-cr-wordpress" - location = var.region - - template { - spec { - containers { - image = var.wordpress_image - ports { - container_port = 80 - } - env { - name = "DB_HOST" - value = "34.91.36.235" - } - env { - name = "DB_USER" - value = "admin" - } - env { - name = "DB_PASSWORD" - value = "password" - } - env { - name = "DB_NAME" - value = "wp-mysql" - } - env { - name = "WORDPRESS_DEBUG" - value = "1" - } - } - } - - metadata { - annotations = { - #"autoscaling.knative.dev/maxScale" = "10" - "run.googleapis.com/cloudsql-instances" = module.cloudsql.connection_name - "run.googleapis.com/client-name" = "terraform" - } - } - } -} - # Grant Cloud Run usage rights to someone who is authorized to access the end-point resource "google_cloud_run_service_iam_member" "cloud_run_iam_member" { project = module.project.project_id @@ -180,7 +138,7 @@ resource "google_cloud_run_service_iam_member" "cloud_run_iam_member" { module "vpc" { source = "../../../../modules/net-vpc" project_id = module.project.project_id - name = "vpc" + name = "${local.prefix}sql-vpc" subnets = [ { ip_cidr_range = local.sql_vpc_cidr @@ -207,17 +165,16 @@ module "nat" { source = "../../../../modules/net-cloudnat" project_id = module.project.project_id region = var.region - name = "${var.prefix}-default" + name = "${local.prefix}nat" router_network = module.vpc.name } module "cloudsql" { source = "../../../../modules/cloudsql-instance" project_id = module.project.project_id - availability_type = local.cloud_sql_conf.availability_type +# availability_type = local.cloud_sql_conf.availability_type network = module.vpc.self_link - # network = data.google_compute_network.default.id - name = "${var.prefix}-mysql" + name = "${local.prefix}mysql" region = var.region database_version = local.cloud_sql_conf.database_version tier = local.cloud_sql_conf.tier @@ -225,23 +182,11 @@ module "cloudsql" { users = { "${local.cloud_sql_conf.user}" = "${local.cloud_sql_conf.pass}" } -# backup_configuration = { -# enabled = true -# binary_log_enabled = true -# start_time = "23:00" -# location = var.region -# log_retention_days = 7 -# retention_count = 7 -# } # authorized_networks = { # internet = "0.0.0.0/0" # } } -#data "google_compute_network" "default" { -# name = "default" -#} - /* resource "google_compute_global_address" "private_ip_address" { name = "private-ip-address" diff --git a/examples/third-party-solutions/wordpress/cloudrun/outputs.tf b/examples/third-party-solutions/wordpress/cloudrun/outputs.tf new file mode 100644 index 00000000..11a2ddf1 --- /dev/null +++ b/examples/third-party-solutions/wordpress/cloudrun/outputs.tf @@ -0,0 +1,15 @@ +/** + * 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. + */ diff --git a/examples/third-party-solutions/wordpress/cloudrun/terraform.tfvars.sample b/examples/third-party-solutions/wordpress/cloudrun/terraform.tfvars.sample new file mode 100644 index 00000000..6f02b126 --- /dev/null +++ b/examples/third-party-solutions/wordpress/cloudrun/terraform.tfvars.sample @@ -0,0 +1,2 @@ +prefix = "wp" +project_id = "nstrelkova-joonix-playground" \ No newline at end of file diff --git a/examples/third-party-solutions/wordpress/cloudrun/variables.tf b/examples/third-party-solutions/wordpress/cloudrun/variables.tf index 415d0444..ce70ac78 100644 --- a/examples/third-party-solutions/wordpress/cloudrun/variables.tf +++ b/examples/third-party-solutions/wordpress/cloudrun/variables.tf @@ -17,6 +17,7 @@ variable "prefix" { description = "Unique prefix used for resource names. Not used for project if 'project_create' is null." type = string + default = "" } variable "project_create" { @@ -30,7 +31,7 @@ variable "project_create" { variable "project_id" { description = "Project id, references existing project if `project_create` is null." - type = string + type = string # TODO: check locals } variable "region" { From 216afda2ce512f162a3daf7d9deaee79514b54a9 Mon Sep 17 00:00:00 2001 From: Benjamin Sadik Date: Thu, 25 Aug 2022 17:16:43 +0200 Subject: [PATCH 05/41] update readme --- .../wordpress/cloudrun/README.md | 99 +++++++++++++++++- .../cloudrun/images/architecture.png | Bin 0 -> 37443 bytes .../wordpress/cloudrun/images/button.png | Bin 0 -> 10762 bytes 3 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 examples/third-party-solutions/wordpress/cloudrun/images/architecture.png create mode 100644 examples/third-party-solutions/wordpress/cloudrun/images/button.png diff --git a/examples/third-party-solutions/wordpress/cloudrun/README.md b/examples/third-party-solutions/wordpress/cloudrun/README.md index 46409041..1323ae12 100644 --- a/examples/third-party-solutions/wordpress/cloudrun/README.md +++ b/examples/third-party-solutions/wordpress/cloudrun/README.md @@ -1 +1,98 @@ -# TODO +# Wordpress deployment on Cloud Run + +43% of the Web is built on Wordpress. Because of its simplicity and versatility, Wordpress can be used for internal websites as well as customer facing e-commerce platforms in small to large businesses while still offering security. + +This repository contains the necessary Terraform files to deploy a functioning new Wordpress website exposed to the public internet with minimal technical overhead. + +This architecture can be used for the following use cases and more: + +* Blog +* Intranet / internal Wiki +* Ecommerce platform + +## Architecture + +![Wordpress on Cloud Run](images/architecture.png "Wordpress on Cloud Run") + +The main components that are deployed in this architecture are the following (you can learn about them by following the hyperlinks): + +* [Cloud Run](https://cloud.google.com/run): serverless PaaS offering to host containers for web oriented applications while offering security, scalability and easy versioning. +* [Cloud SQL](https://cloud.google.com/sql): Managed solution for SQL DB + +## Setup + +### Prerequisites + +#### Setting up the project for the deployment + +This example will deploy all its resources into the project defined by the `project_id` variable. Please note that we assume this project already exists. However, if you provide the appropriate values to the `project_create` variable, the project will be created as part of the deployment. + +If `project_create` is left to null, the identity performing the deployment needs the `owner` role on the project defined by the `project_id` variable. Otherwise, the identity performing the deployment needs `resourcemanager.projectCreator` on the resource hierarchy node specified by `project_create.parent` and `billing.user` on the billing account specified by `project_create.billing_account_id`. + +### Deployment + +#### Step 0: Cloning the repository + +Click on the image below, sign in if required and when the prompt appears, click on “confirm”. + +[

Open Cloudshell

]() + +LINK NEEDED + +Before we deploy the architecture, you will at least need the following information (for more precise configuration see the Variables section): + +* The project Id. +* A Google Cloud Registry path to a Wordpress container image. + +#### Step 1: Build Wordpress image + +In order to deploy the Wordpress service to Cloud Run, you need to build and store the image in Google Cloud Registry (GCR). + +Make sure that the GCR API is enabled and run the following commands in your Cloud Shell environment with your `project_id` in place of the `MY_PROJECT` placeholder: + +``` {shell} +docker pull wordpress +docker tag wordpress gcr.io/MY_PROJECT/busybox +docker push gcr.io/MY_PROJECT/wordpress +``` + +#### Step 2: Deploy resources + +Once you have the required information, head back to the Cloud Shell editor. Make sure you’re in the following directory: `cloudshell_open/cloud-foundation-fabric/examples/third-party-solutions/wordpress/cloudrun/`. + +Configure the Terraform variables in your terraform.tfvars file. See [terraform.tfvars.sample](terraform.tfvars.sample) as starting point. + +Initialize your Terraform environment: + +``` {shell} +alias tf=terraform +tf init +tf apply -var-file="terraform.tfvars.sample" +``` + +The resource creation will take a few minutes, at the end this is the output you should expect for successful completion along with a list of the created resources. + +#### Clean up your environment + +The easiest way to remove all the deployed resources is to run the following command in Cloud Shell: + +``` {shell} +tf destroy -var-file="terraform.tfvars.sample" +``` + +The above command will delete the associated resources so there will be no billable charges made afterwards. + + +## Variables + +| name | description | type | required | default | +|---|---|:---:|:---:|:---:| +| [project_id](variables.tf#L32) | Project id, references existing project if `project_create` is null. | string # TODO: check locals | ✓ | | +| [wordpress_image](variables.tf#L49) | Image to run with Cloud Run, starts with \"gcr.io\" | string | ✓ | | +| [cloud_run_invoker](variables.tf#L55) | IAM member authorized to access the end-point (for example, 'user:YOUR_IAM_USER' for only you or 'allUsers' for everyone) | string | | "allUsers" | +| [prefix](variables.tf#L17) | Unique prefix used for resource names. Not used for project if 'project_create' is null. | string | | "" | +| [principals](variables.tf#L43) | List of emails of people/service accounts to give rights to, eg 'user@domain.com'. | list(string) | | [] | +| [project_create](variables.tf#L23) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | | null | +| [region](variables.tf#L37) | Region for the created resources | string | | "europe-west4" | + + diff --git a/examples/third-party-solutions/wordpress/cloudrun/images/architecture.png b/examples/third-party-solutions/wordpress/cloudrun/images/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..cdbc07964ee328856fc82e51801e5c46d205920d GIT binary patch literal 37443 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 Date: Wed, 31 Aug 2022 14:19:54 +0000 Subject: [PATCH 06/41] more code refactoring --- .../wordpress/cloudrun/README.md | 40 ++-- .../wordpress/cloudrun/main.tf | 220 +++++------------- .../wordpress/cloudrun/outputs.tf | 17 ++ .../cloudrun/terraform.tfvars.sample | 2 +- .../wordpress/cloudrun/variables.tf | 27 ++- 5 files changed, 123 insertions(+), 183 deletions(-) diff --git a/examples/third-party-solutions/wordpress/cloudrun/README.md b/examples/third-party-solutions/wordpress/cloudrun/README.md index 1323ae12..dfd57589 100644 --- a/examples/third-party-solutions/wordpress/cloudrun/README.md +++ b/examples/third-party-solutions/wordpress/cloudrun/README.md @@ -1,6 +1,6 @@ # Wordpress deployment on Cloud Run -43% of the Web is built on Wordpress. Because of its simplicity and versatility, Wordpress can be used for internal websites as well as customer facing e-commerce platforms in small to large businesses while still offering security. +43% of the Web is built on Wordpress. Because of its simplicity and versatility, Wordpress can be used for internal websites as well as customer facing e-commerce platforms in small to large businesses, while still offering security. This repository contains the necessary Terraform files to deploy a functioning new Wordpress website exposed to the public internet with minimal technical overhead. @@ -8,7 +8,7 @@ This architecture can be used for the following use cases and more: * Blog * Intranet / internal Wiki -* Ecommerce platform +* E-commerce platform ## Architecture @@ -16,8 +16,8 @@ This architecture can be used for the following use cases and more: The main components that are deployed in this architecture are the following (you can learn about them by following the hyperlinks): -* [Cloud Run](https://cloud.google.com/run): serverless PaaS offering to host containers for web oriented applications while offering security, scalability and easy versioning. -* [Cloud SQL](https://cloud.google.com/sql): Managed solution for SQL DB +* [Cloud Run](https://cloud.google.com/run): serverless PaaS offering to host containers for web-oriented applications, while offering security, scalability and easy versioning +* [Cloud SQL](https://cloud.google.com/sql): Managed solution for SQL databases ## Setup @@ -41,43 +41,51 @@ LINK NEEDED Before we deploy the architecture, you will at least need the following information (for more precise configuration see the Variables section): -* The project Id. +* The project ID. * A Google Cloud Registry path to a Wordpress container image. -#### Step 1: Build Wordpress image +#### Step 1: Add Wordpress image -In order to deploy the Wordpress service to Cloud Run, you need to build and store the image in Google Cloud Registry (GCR). +In order to deploy the Wordpress service to Cloud Run, you need to store the [Wordpress image](https://hub.docker.com/r/bitnami/wordpress/) in Google Cloud Registry (GCR). Make sure that the GCR API is enabled and run the following commands in your Cloud Shell environment with your `project_id` in place of the `MY_PROJECT` placeholder: ``` {shell} -docker pull wordpress -docker tag wordpress gcr.io/MY_PROJECT/busybox +docker pull bitnami/wordpress +docker tag bitnami/wordpress gcr.io/MY_PROJECT/busybox docker push gcr.io/MY_PROJECT/wordpress ``` + #### Step 2: Deploy resources Once you have the required information, head back to the Cloud Shell editor. Make sure you’re in the following directory: `cloudshell_open/cloud-foundation-fabric/examples/third-party-solutions/wordpress/cloudrun/`. -Configure the Terraform variables in your terraform.tfvars file. See [terraform.tfvars.sample](terraform.tfvars.sample) as starting point. +Configure the Terraform variables in your terraform.tfvars file. See [terraform.tfvars.sample](terraform.tfvars.sample) as starting point - just copy it to `terraform.tfvars` and edit the latter. -Initialize your Terraform environment: +Initialize your Terraform environment and deploy the resources: ``` {shell} -alias tf=terraform -tf init -tf apply -var-file="terraform.tfvars.sample" +terraform init +terraform apply +``` + +The resource creation will take a few minutes. + +Upon completion, you will see the output with the values for the Cloud Run service and the user and password to access the `/admin` part of the website. You can also view it later with: +``` {shell} +terraform output +# or for the concrete variable: +terraform output cloud_run_service ``` -The resource creation will take a few minutes, at the end this is the output you should expect for successful completion along with a list of the created resources. #### Clean up your environment The easiest way to remove all the deployed resources is to run the following command in Cloud Shell: ``` {shell} -tf destroy -var-file="terraform.tfvars.sample" +tf destroy ``` The above command will delete the associated resources so there will be no billable charges made afterwards. diff --git a/examples/third-party-solutions/wordpress/cloudrun/main.tf b/examples/third-party-solutions/wordpress/cloudrun/main.tf index 09756920..84a1f0db 100644 --- a/examples/third-party-solutions/wordpress/cloudrun/main.tf +++ b/examples/third-party-solutions/wordpress/cloudrun/main.tf @@ -22,34 +22,26 @@ locals { ] iam = { # CloudSQL - "roles/cloudsql.admin" = local.all_principals_iam - "roles/cloudsql.client" = concat( - local.all_principals_iam, - #[module.service-account-sql.iam_email] - ) - "roles/cloudsql.instanceUser" = concat( - local.all_principals_iam, - #[module.service-account-sql.iam_email] - ) + "roles/cloudsql.admin" = local.all_principals_iam + "roles/cloudsql.client" = local.all_principals_iam + "roles/cloudsql.instanceUser" = local.all_principals_iam # common roles - "roles/logging.admin" = local.all_principals_iam - "roles/iam.serviceAccountUser" = local.all_principals_iam + "roles/logging.admin" = local.all_principals_iam + "roles/iam.serviceAccountUser" = local.all_principals_iam "roles/iam.serviceAccountTokenCreator" = local.all_principals_iam } cloud_sql_conf = { - availability_type = "ZONAL" database_version = "MYSQL_8_0" - psa_range = "10.60.0.0/16" tier = "db-g1-small" db = "wp-mysql" user = "admin" - pass = "password" # TODO: var + pass = "password" } - sql_vpc_cidr = "10.0.0.0/20" - connector_cidr = "10.8.0.0/28" # !!! + wp_user = "user" } -module "project" { + +module "project" { # either create a project or set up the given one source = "../../../../modules/project" name = var.project_id parent = try(var.project_create.parent, null) @@ -64,50 +56,39 @@ module "project" { "monitoring.googleapis.com", "sqladmin.googleapis.com", "sql-component.googleapis.com", - #"storage.googleapis.com", - #"storage-component.googleapis.com", "vpcaccess.googleapis.com" ] -# service_config = { -# disable_on_destroy = false, -# disable_dependent_services = false -# } } -module "cloud_run" { +resource "random_password" "wp_password" { + length = 8 +} + +module "cloud_run" { # create the Cloud Run service source = "../../../../modules/cloud-run" project_id = module.project.project_id name = "${local.prefix}cr-wordpress" region = var.region - cloudsql_instances = module.cloudsql.connection_name - vpc_connector = { - create = true - name = "${local.prefix}wp-connector" - egress_settings = "all-traffic" - } - vpc_connector_config = { - network = module.vpc.self_link - ip_cidr_range = local.connector_cidr - } -# "run.googleapis.com/ingress" = "internal-and-cloud-load-balancing" containers = [{ image = var.wordpress_image - ports = [{ # TODO https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_service + ports = [{ name = "http1" - protocol = "TCP" - container_port = 80 + protocol = null + container_port = var.wordpress_port }] options = { command = null args = null env_from = null - env = { - "DB_HOST" : module.cloudsql.ip - "DB_USER" : local.cloud_sql_conf.user - "DB_PASSWORD" : local.cloud_sql_conf.pass - "DB_NAME" : local.cloud_sql_conf.db - # "WORDPRESS_DEBUG": "1" + env = { # set up the database connection + "APACHE_HTTP_PORT_NUMBER" : var.wordpress_port + "WORDPRESS_DATABASE_HOST" : module.cloudsql.ip + "WORDPRESS_DATABASE_NAME" : local.cloud_sql_conf.db + "WORDPRESS_DATABASE_USER" : local.cloud_sql_conf.user + "WORDPRESS_DATABASE_PASSWORD": local.cloud_sql_conf.pass + "WORDPRESS_USERNAME" : local.wp_user + "WORDPRESS_PASSWORD" : random_password.wp_password.result } } resources = null @@ -117,62 +98,60 @@ module "cloud_run" { iam = { "roles/run.invoker": [var.cloud_run_invoker] } + + revision_annotations = { + autoscaling = { + min_scale = 1 + max_scale = 2 + } + # connect to CloudSQL + cloudsql_instances = [ module.cloudsql.connection_name ] + vpcaccess_connector = null + vpcaccess_egress = "all-traffic" # allow all traffic + } + ingress_settings = "all" + + vpc_connector_create = { # create a VPC connector for the ClouSQL VPC + ip_cidr_range = var.connector_cidr + name = "${local.prefix}wp-connector" + vpc_self_link = module.vpc.self_link + } } -# tftest modules=1 resources=1 - -/* -# Grant Cloud Run usage rights to someone who is authorized to access the end-point -resource "google_cloud_run_service_iam_member" "cloud_run_iam_member" { - project = module.project.project_id - location = google_cloud_run_service.cloud_run.location - service = google_cloud_run_service.cloud_run.name - - role = "roles/run.invoker" - - member = var.cloud_run_invoker -} -*/ - -module "vpc" { +module "vpc" { # create a VPC for CloudSQL source = "../../../../modules/net-vpc" project_id = module.project.project_id name = "${local.prefix}sql-vpc" - subnets = [ + subnets = [ { - ip_cidr_range = local.sql_vpc_cidr + ip_cidr_range = var.sql_vpc_cidr name = "subnet" region = var.region secondary_ip_range = {} } ] - psa_config = { - ranges = { cloud-sql = local.cloud_sql_conf.psa_range } + psa_config = { # Private Service Access + ranges = { + cloud-sql = var.psa_cidr + } routes = null } } -module "firewall" { + +module "firewall" { # set up firewall for CloudSQL source = "../../../../modules/net-vpc-firewall" project_id = module.project.project_id network = module.vpc.name - admin_ranges = [local.sql_vpc_cidr] + admin_ranges = [var.sql_vpc_cidr] } -module "nat" { - source = "../../../../modules/net-cloudnat" - project_id = module.project.project_id - region = var.region - name = "${local.prefix}nat" - router_network = module.vpc.name -} -module "cloudsql" { +module "cloudsql" { # Set up CloudSQL source = "../../../../modules/cloudsql-instance" project_id = module.project.project_id -# availability_type = local.cloud_sql_conf.availability_type network = module.vpc.self_link name = "${local.prefix}mysql" region = var.region @@ -182,93 +161,4 @@ module "cloudsql" { users = { "${local.cloud_sql_conf.user}" = "${local.cloud_sql_conf.pass}" } -# authorized_networks = { -# internet = "0.0.0.0/0" -# } -} - -/* -resource "google_compute_global_address" "private_ip_address" { - name = "private-ip-address" - purpose = "VPC_PEERING" - address_type = "INTERNAL" - prefix_length = 16 - network = module.vpc.self_link - #network = data.google_compute_network.default.id -} - -resource "google_service_networking_connection" "private_vpc_connection" { - network = module.vpc.self_link - #network = data.google_compute_network.default.id - service = "servicenetworking.googleapis.com" - reserved_peering_ranges = [google_compute_global_address.private_ip_address.name] -} -*/ - -# ------------------------------------------------------------ -/* -resource "google_compute_global_address" "default" { - name = "${var.wordpress_project_name}-address" -} - -# #OPTIONAL LB START - -#or use your own cert here -#certificate can take up to 24h to provision -resource "google_compute_managed_ssl_certificate" "default" { - name = "${var.wordpress_project_name}-cert" - managed { - domains = ["nip.io"]#tovars - } -} - - -resource "google_compute_region_network_endpoint_group" "cloudrun_neg" { - name = "${var.wordpress_project_name}-neg" - network_endpoint_type = "SERVERLESS" - region = var.region - cloud_run { - service = google_cloud_run_service.cloud_run.name - } -} - -module "lb-http" { - source = "GoogleCloudPlatform/lb-http/google//modules/serverless_negs" - project = var.project - - name = "${var.wordpress_project_name}-lb" - - managed_ssl_certificate_domains = ["nip.io"] - ssl = true - https_redirect = true - - backends = { - default = { - groups = [ - { - group = google_compute_region_network_endpoint_group.cloudrun_neg.id - } - ] - - enable_cdn = false - - log_config = { - enable = false - sample_rate = 0.0 - } - - iap_config = { - enable = false - oauth2_client_id = null - oauth2_client_secret = null - } - - description = null - custom_request_headers = null - custom_response_headers = null - security_policy = null - } - } -} -#END OPTIONAL LB -*/ +} \ No newline at end of file diff --git a/examples/third-party-solutions/wordpress/cloudrun/outputs.tf b/examples/third-party-solutions/wordpress/cloudrun/outputs.tf index 11a2ddf1..ce993660 100644 --- a/examples/third-party-solutions/wordpress/cloudrun/outputs.tf +++ b/examples/third-party-solutions/wordpress/cloudrun/outputs.tf @@ -13,3 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +output "cloud_run_service" { + description = "CloudRun service URL" + value = module.cloud_run.service.status[0].url + sensitive = true +} + +output "wp_user" { + description = "Wordpress username" + value = local.wp_user +} + +output "wp_password" { + description = "Wordpress user password" + value = random_password.wp_password.result + sensitive = true +} diff --git a/examples/third-party-solutions/wordpress/cloudrun/terraform.tfvars.sample b/examples/third-party-solutions/wordpress/cloudrun/terraform.tfvars.sample index 6f02b126..0c7292b7 100644 --- a/examples/third-party-solutions/wordpress/cloudrun/terraform.tfvars.sample +++ b/examples/third-party-solutions/wordpress/cloudrun/terraform.tfvars.sample @@ -1,2 +1,2 @@ prefix = "wp" -project_id = "nstrelkova-joonix-playground" \ No newline at end of file +project_id = "my-wordpress-project" \ No newline at end of file diff --git a/examples/third-party-solutions/wordpress/cloudrun/variables.tf b/examples/third-party-solutions/wordpress/cloudrun/variables.tf index ce70ac78..0823b263 100644 --- a/examples/third-party-solutions/wordpress/cloudrun/variables.tf +++ b/examples/third-party-solutions/wordpress/cloudrun/variables.tf @@ -31,7 +31,7 @@ variable "project_create" { variable "project_id" { description = "Project id, references existing project if `project_create` is null." - type = string # TODO: check locals + type = string } variable "region" { @@ -51,9 +51,34 @@ variable "wordpress_image" { description = "Image to run with Cloud Run, starts with \"gcr.io\"" } +variable "wordpress_port" { + type = number + description = "Port for the Wordpress image (8080 by default)" + default = 8080 +} + # Documentation: https://cloud.google.com/run/docs/securing/managing-access#making_a_service_public variable "cloud_run_invoker" { type = string description = "IAM member authorized to access the end-point (for example, 'user:YOUR_IAM_USER' for only you or 'allUsers' for everyone)" default = "allUsers" +} + +variable "connector_cidr" { + type = string + description = "CIDR block for the VPC serverless connector (10.8.0.0/28 by default)" + default = "10.8.0.0/28" +} + +variable "sql_vpc_cidr" { + type = string + description = "CIDR block for the VPC for the CloudSQL (10.0.0.0/20 by default)" + default = "10.0.0.0/20" +} + +# Documentation: https://cloud.google.com/vpc/docs/configure-private-services-access#allocating-range +variable "psa_cidr" { + type = string + description = "CIDR block for Private Service Access for CloudSQL (10.60.0.0/24 by default)" + default = "10.60.0.0/24" } \ No newline at end of file From e6086816c5d0c86410e959af83aac1127458d2d5 Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Wed, 31 Aug 2022 14:29:16 +0000 Subject: [PATCH 07/41] formatting --- .../wordpress/cloudrun/main.tf | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/examples/third-party-solutions/wordpress/cloudrun/main.tf b/examples/third-party-solutions/wordpress/cloudrun/main.tf index 84a1f0db..4080f6aa 100644 --- a/examples/third-party-solutions/wordpress/cloudrun/main.tf +++ b/examples/third-party-solutions/wordpress/cloudrun/main.tf @@ -22,20 +22,20 @@ locals { ] iam = { # CloudSQL - "roles/cloudsql.admin" = local.all_principals_iam - "roles/cloudsql.client" = local.all_principals_iam - "roles/cloudsql.instanceUser" = local.all_principals_iam + "roles/cloudsql.admin" = local.all_principals_iam + "roles/cloudsql.client" = local.all_principals_iam + "roles/cloudsql.instanceUser" = local.all_principals_iam # common roles "roles/logging.admin" = local.all_principals_iam "roles/iam.serviceAccountUser" = local.all_principals_iam "roles/iam.serviceAccountTokenCreator" = local.all_principals_iam } cloud_sql_conf = { - database_version = "MYSQL_8_0" - tier = "db-g1-small" - db = "wp-mysql" - user = "admin" - pass = "password" + database_version = "MYSQL_8_0" + tier = "db-g1-small" + db = "wp-mysql" + user = "admin" + pass = "password" } wp_user = "user" } @@ -67,8 +67,8 @@ resource "random_password" "wp_password" { module "cloud_run" { # create the Cloud Run service source = "../../../../modules/cloud-run" project_id = module.project.project_id - name = "${local.prefix}cr-wordpress" - region = var.region + name = "${local.prefix}cr-wordpress" + region = var.region containers = [{ image = var.wordpress_image @@ -81,22 +81,22 @@ module "cloud_run" { # create the Cloud Run service command = null args = null env_from = null - env = { # set up the database connection - "APACHE_HTTP_PORT_NUMBER" : var.wordpress_port - "WORDPRESS_DATABASE_HOST" : module.cloudsql.ip - "WORDPRESS_DATABASE_NAME" : local.cloud_sql_conf.db - "WORDPRESS_DATABASE_USER" : local.cloud_sql_conf.user - "WORDPRESS_DATABASE_PASSWORD": local.cloud_sql_conf.pass - "WORDPRESS_USERNAME" : local.wp_user - "WORDPRESS_PASSWORD" : random_password.wp_password.result + env = { # set up the database connection + "APACHE_HTTP_PORT_NUMBER" : var.wordpress_port + "WORDPRESS_DATABASE_HOST" : module.cloudsql.ip + "WORDPRESS_DATABASE_NAME" : local.cloud_sql_conf.db + "WORDPRESS_DATABASE_USER" : local.cloud_sql_conf.user + "WORDPRESS_DATABASE_PASSWORD" : local.cloud_sql_conf.pass + "WORDPRESS_USERNAME" : local.wp_user + "WORDPRESS_PASSWORD" : random_password.wp_password.result } } - resources = null + resources = null volume_mounts = null }] iam = { - "roles/run.invoker": [var.cloud_run_invoker] + "roles/run.invoker" : [var.cloud_run_invoker] } revision_annotations = { @@ -105,7 +105,7 @@ module "cloud_run" { # create the Cloud Run service max_scale = 2 } # connect to CloudSQL - cloudsql_instances = [ module.cloudsql.connection_name ] + cloudsql_instances = [module.cloudsql.connection_name] vpcaccess_connector = null vpcaccess_egress = "all-traffic" # allow all traffic } @@ -123,7 +123,7 @@ module "vpc" { # create a VPC for CloudSQL source = "../../../../modules/net-vpc" project_id = module.project.project_id name = "${local.prefix}sql-vpc" - subnets = [ + subnets = [ { ip_cidr_range = var.sql_vpc_cidr name = "subnet" @@ -150,15 +150,15 @@ module "firewall" { # set up firewall for CloudSQL module "cloudsql" { # Set up CloudSQL - source = "../../../../modules/cloudsql-instance" - project_id = module.project.project_id - network = module.vpc.self_link - name = "${local.prefix}mysql" - region = var.region - database_version = local.cloud_sql_conf.database_version - tier = local.cloud_sql_conf.tier - databases = [local.cloud_sql_conf.db] + source = "../../../../modules/cloudsql-instance" + project_id = module.project.project_id + network = module.vpc.self_link + name = "${local.prefix}mysql" + region = var.region + database_version = local.cloud_sql_conf.database_version + tier = local.cloud_sql_conf.tier + databases = [local.cloud_sql_conf.db] users = { - "${local.cloud_sql_conf.user}" = "${local.cloud_sql_conf.pass}" + "${local.cloud_sql_conf.user}" = "${local.cloud_sql_conf.pass}" } } \ No newline at end of file From 8464fc9b01438cf42fa2c112420d2b3d5eb46222 Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Wed, 31 Aug 2022 14:32:02 +0000 Subject: [PATCH 08/41] variables and outputs from tfdoc --- .../wordpress/cloudrun/README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/examples/third-party-solutions/wordpress/cloudrun/README.md b/examples/third-party-solutions/wordpress/cloudrun/README.md index dfd57589..939fb510 100644 --- a/examples/third-party-solutions/wordpress/cloudrun/README.md +++ b/examples/third-party-solutions/wordpress/cloudrun/README.md @@ -95,12 +95,24 @@ The above command will delete the associated resources so there will be no billa | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [project_id](variables.tf#L32) | Project id, references existing project if `project_create` is null. | string # TODO: check locals | ✓ | | +| [project_id](variables.tf#L32) | Project id, references existing project if `project_create` is null. | string | ✓ | | | [wordpress_image](variables.tf#L49) | Image to run with Cloud Run, starts with \"gcr.io\" | string | ✓ | | -| [cloud_run_invoker](variables.tf#L55) | IAM member authorized to access the end-point (for example, 'user:YOUR_IAM_USER' for only you or 'allUsers' for everyone) | string | | "allUsers" | +| [cloud_run_invoker](variables.tf#L61) | IAM member authorized to access the end-point (for example, 'user:YOUR_IAM_USER' for only you or 'allUsers' for everyone) | string | | "allUsers" | +| [connector_cidr](variables.tf#L67) | CIDR block for the VPC serverless connector (10.8.0.0/28 by default) | string | | "10.8.0.0/28" | | [prefix](variables.tf#L17) | Unique prefix used for resource names. Not used for project if 'project_create' is null. | string | | "" | | [principals](variables.tf#L43) | List of emails of people/service accounts to give rights to, eg 'user@domain.com'. | list(string) | | [] | | [project_create](variables.tf#L23) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | | null | +| [psa_cidr](variables.tf#L80) | CIDR block for Private Service Access for CloudSQL (10.60.0.0/24 by default) | string | | "10.60.0.0/24" | | [region](variables.tf#L37) | Region for the created resources | string | | "europe-west4" | +| [sql_vpc_cidr](variables.tf#L73) | CIDR block for the VPC for the CloudSQL (10.0.0.0/20 by default) | string | | "10.0.0.0/20" | +| [wordpress_port](variables.tf#L54) | Port for the Wordpress image (8080 by default) | number | | 8080 | + +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| [cloud_run_service](outputs.tf#L17) | CloudRun service URL | ✓ | +| [wp_password](outputs.tf#L28) | Wordpress user password | ✓ | +| [wp_user](outputs.tf#L23) | Wordpress username | | From 88aa76842196c44647df9322f558367b5812a35c Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Fri, 9 Sep 2022 08:17:47 +0000 Subject: [PATCH 09/41] missing API adedd --- examples/third-party-solutions/wordpress/cloudrun/main.tf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/third-party-solutions/wordpress/cloudrun/main.tf b/examples/third-party-solutions/wordpress/cloudrun/main.tf index 4080f6aa..6c263c4c 100644 --- a/examples/third-party-solutions/wordpress/cloudrun/main.tf +++ b/examples/third-party-solutions/wordpress/cloudrun/main.tf @@ -56,7 +56,8 @@ module "project" { # either create a project or set up the given one "monitoring.googleapis.com", "sqladmin.googleapis.com", "sql-component.googleapis.com", - "vpcaccess.googleapis.com" + "vpcaccess.googleapis.com", + "servicenetworking.googleapis.com" ] } From fa68ed4ce1ebb785941b3862efef683fa59dc26f Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Thu, 15 Sep 2022 12:17:45 +0000 Subject: [PATCH 10/41] moving Wordpress to the right folder --- .../third-party-solutions/wordpress/README.md | 0 .../wordpress/cloudrun/README.md | 0 .../wordpress/cloudrun/images/architecture.png | Bin .../wordpress/cloudrun/images/button.png | Bin .../wordpress/cloudrun/main.tf | 0 .../wordpress/cloudrun/outputs.tf | 0 .../wordpress/cloudrun/terraform.tfvars.sample | 0 .../wordpress/cloudrun/variables.tf | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename {examples => blueprints}/third-party-solutions/wordpress/README.md (100%) rename {examples => blueprints}/third-party-solutions/wordpress/cloudrun/README.md (100%) rename {examples => blueprints}/third-party-solutions/wordpress/cloudrun/images/architecture.png (100%) rename {examples => blueprints}/third-party-solutions/wordpress/cloudrun/images/button.png (100%) rename {examples => blueprints}/third-party-solutions/wordpress/cloudrun/main.tf (100%) rename {examples => blueprints}/third-party-solutions/wordpress/cloudrun/outputs.tf (100%) rename {examples => blueprints}/third-party-solutions/wordpress/cloudrun/terraform.tfvars.sample (100%) rename {examples => blueprints}/third-party-solutions/wordpress/cloudrun/variables.tf (100%) diff --git a/examples/third-party-solutions/wordpress/README.md b/blueprints/third-party-solutions/wordpress/README.md similarity index 100% rename from examples/third-party-solutions/wordpress/README.md rename to blueprints/third-party-solutions/wordpress/README.md diff --git a/examples/third-party-solutions/wordpress/cloudrun/README.md b/blueprints/third-party-solutions/wordpress/cloudrun/README.md similarity index 100% rename from examples/third-party-solutions/wordpress/cloudrun/README.md rename to blueprints/third-party-solutions/wordpress/cloudrun/README.md diff --git a/examples/third-party-solutions/wordpress/cloudrun/images/architecture.png b/blueprints/third-party-solutions/wordpress/cloudrun/images/architecture.png similarity index 100% rename from examples/third-party-solutions/wordpress/cloudrun/images/architecture.png rename to blueprints/third-party-solutions/wordpress/cloudrun/images/architecture.png diff --git a/examples/third-party-solutions/wordpress/cloudrun/images/button.png b/blueprints/third-party-solutions/wordpress/cloudrun/images/button.png similarity index 100% rename from examples/third-party-solutions/wordpress/cloudrun/images/button.png rename to blueprints/third-party-solutions/wordpress/cloudrun/images/button.png diff --git a/examples/third-party-solutions/wordpress/cloudrun/main.tf b/blueprints/third-party-solutions/wordpress/cloudrun/main.tf similarity index 100% rename from examples/third-party-solutions/wordpress/cloudrun/main.tf rename to blueprints/third-party-solutions/wordpress/cloudrun/main.tf diff --git a/examples/third-party-solutions/wordpress/cloudrun/outputs.tf b/blueprints/third-party-solutions/wordpress/cloudrun/outputs.tf similarity index 100% rename from examples/third-party-solutions/wordpress/cloudrun/outputs.tf rename to blueprints/third-party-solutions/wordpress/cloudrun/outputs.tf diff --git a/examples/third-party-solutions/wordpress/cloudrun/terraform.tfvars.sample b/blueprints/third-party-solutions/wordpress/cloudrun/terraform.tfvars.sample similarity index 100% rename from examples/third-party-solutions/wordpress/cloudrun/terraform.tfvars.sample rename to blueprints/third-party-solutions/wordpress/cloudrun/terraform.tfvars.sample diff --git a/examples/third-party-solutions/wordpress/cloudrun/variables.tf b/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf similarity index 100% rename from examples/third-party-solutions/wordpress/cloudrun/variables.tf rename to blueprints/third-party-solutions/wordpress/cloudrun/variables.tf From 2aefab030a428477b6350b0f4003136531de8851 Mon Sep 17 00:00:00 2001 From: Eva Date: Thu, 15 Sep 2022 12:20:05 +0000 Subject: [PATCH 11/41] Changes to README, vars samples and architecture diagram --- .../wordpress/cloudrun/README.md | 18 ++++++++++++------ .../cloudrun/images/architecture.png | Bin 37443 -> 43505 bytes .../cloudrun/terraform.tfvars.sample | 3 ++- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/examples/third-party-solutions/wordpress/cloudrun/README.md b/examples/third-party-solutions/wordpress/cloudrun/README.md index 939fb510..b0f91059 100644 --- a/examples/third-party-solutions/wordpress/cloudrun/README.md +++ b/examples/third-party-solutions/wordpress/cloudrun/README.md @@ -37,7 +37,7 @@ Click on the image below, sign in if required and when the prompt appears, click [

Open Cloudshell

]() -LINK NEEDED +LINK NEEDED --> can only be added after PR Before we deploy the architecture, you will at least need the following information (for more precise configuration see the Variables section): @@ -48,18 +48,25 @@ Before we deploy the architecture, you will at least need the following informat In order to deploy the Wordpress service to Cloud Run, you need to store the [Wordpress image](https://hub.docker.com/r/bitnami/wordpress/) in Google Cloud Registry (GCR). -Make sure that the GCR API is enabled and run the following commands in your Cloud Shell environment with your `project_id` in place of the `MY_PROJECT` placeholder: +Make sure that the Google Container Registry API is enabled and run the following commands in your Cloud Shell environment with your `project_id` in place of the `MY_PROJECT` placeholder: ``` {shell} docker pull bitnami/wordpress -docker tag bitnami/wordpress gcr.io/MY_PROJECT/busybox +``` + +```{shell} +docker tag bitnami/wordpress gcr.io/MY_PROJECT/wordpress +``` +```{shell + docker push gcr.io/MY_PROJECT/wordpress ``` +** Important : please note this example architecture is built for this particular bitnami image, if you decide to use another one this example might not work.** #### Step 2: Deploy resources -Once you have the required information, head back to the Cloud Shell editor. Make sure you’re in the following directory: `cloudshell_open/cloud-foundation-fabric/examples/third-party-solutions/wordpress/cloudrun/`. +Once you have the required information, head back to the Cloud Shell editor. Make sure you’re in the directory of this tutorial (where this README is in). Configure the Terraform variables in your terraform.tfvars file. See [terraform.tfvars.sample](terraform.tfvars.sample) as starting point - just copy it to `terraform.tfvars` and edit the latter. @@ -69,7 +76,6 @@ Initialize your Terraform environment and deploy the resources: terraform init terraform apply ``` - The resource creation will take a few minutes. Upon completion, you will see the output with the values for the Cloud Run service and the user and password to access the `/admin` part of the website. You can also view it later with: @@ -85,7 +91,7 @@ terraform output cloud_run_service The easiest way to remove all the deployed resources is to run the following command in Cloud Shell: ``` {shell} -tf destroy +terraform destroy ``` The above command will delete the associated resources so there will be no billable charges made afterwards. diff --git a/examples/third-party-solutions/wordpress/cloudrun/images/architecture.png b/examples/third-party-solutions/wordpress/cloudrun/images/architecture.png index cdbc07964ee328856fc82e51801e5c46d205920d..ad914ecc2546e2499a23679dec1fcfd60c9f62cd 100644 GIT binary patch literal 43505 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 literal 37443 zcmce;byQS+_XkQzNO!jq(v7r|N(v&~0wU6#!_X-zigYRSZ4?xA1PThOD>f$Z zj+zUqGVll0O!rZMzvG)KG|vd3JK`_>a=sFh6BD8S^#Z@WwfG9E`OQT)UqKGz`m2rT6?8)3e|?RN zjR}(35mHnd6@(pm{T14}di||Sq7VG2I7l)EV-NMeZX}0s3oe3n{q_JuOkp@GuzcuZ zp*;%|+Fvh7+1>x~E*htrT!(BJmYxae-v>s;0rmfncZpRj&`1!RQgy#l{yq=33e2A1 zuTx@2%H1TL+z3Oqz0`2;_gL&fV-$5f(Zkan<;E(N`5Nby?+2Z6HS1cSCs>R`<$b}4 z)?&00cRIxIqbCga#AW$h(G-4C^f_rFlb2ky*K={jL=d|1f-upklx{-*1mv7SD#n-? zMP1}y_cM1yUyt$R%vfXbsIsymFqM(A$&ln^bIcu#N}onG{w=B)4=d2%S6APd3=BPd z=PQ^uc401#Z_Q_~b(h4=Nh-jXBE?qHxkIPRB6Yy0#!1t5$QDF)CgAXnuq(>WGUR14 zFYguUzGA@e^o4+af*rTU=ECA_eD+o}-#Put-%bIOM%b7eMKRj8XmLhjdblr*cM4UM z4#l@NQTS!M>w_oF*--1c825=`t6=T+9PeWF-qfV=O3xHuSD=itU|)ER+U4ydcn(?czkfL&808Zxk8GDo43Rx^1KAFC+X%mQxMNx8+>ULh^wv@iX;D#60@k)xl zq1rX&z_-Q)!v|hDC)et!Q9QdO#_~+Tmfb(le1wFthC0utpltndSbCOp;FONhSR-CB+u z;9U`QT|D_Y&m=(ma&AzyDeJv=J7XZ}YYWw$Re~yeLxh*>_VBdDFH5RM9=IQpKtsqh z7Bi`~wD3_@-f`$LpB8N4*pAb4bLsv89af+b#Vny8gM8=R@Vm)vD<5TsxabT&3kJP5 zG0x+o)2w-o$-yfNf!Cd7sXett(bT-t3HJ!>BV0%K8O80d?>McuQyx3>tMW{_TkD=n zSUC=Z?$>>lLaaziEm*J8zi-9(Tzl~LAe>=mT6|JQs4#q3WX0J+1*1P***3?^h}|@4 zt9@ovYV0OxzLPFOgXjBh_6-n5Jm>igH*M1Aoc-OJ=FOVZ3PE#D?6on5xik*$@Qr1p z#z5@X<<_UUjCvSU1ofdyT|$-Z6?7-}qbxeCnemW>bY*R)>ZMKbcR_ijQ1CXx^f$Yl zm_Yq4k(1`=KYWoe1~}amMoKfWr!6exp2;`G+9>Wax30#Dy>BQE)d5p_feaW7)mTx( zx*uqgr&8_>@X*(7e|2>+kLkghm8`%^NPLUCt9tCV9AwJ-))%a%clzkPCda)Bth%+y z!ZjQ5N5sEmr52)S3BC#UXm3s3$A_-OTf@9PPemAL6T(E*={WA7yUwD_IPZ^kbuT*4 zJW5s)p1$Aic_jIfJSQPz&nn_66aP@k{6x__=O=!11y{9ojMx=AUnOKi87%>moXZHL# zb6dZ;+E{mNJYz`45Tdz5$7s*J72eg;@i z-EQ^YaUsB{+56?C$}c<^<#z0hB?8WX3T5z#>6?_lr;RwoEk$-3m`WY3kx&S>TzqvE zaEM9g6A3%hxpg2#laJQhC?w^*v$l^gATWxCXSw9E5r94P<)=YxiPSE%`gIs_-YhpD#z4}fU^V*{k4~u0)pf|*a?RQy8RQ@p^zr zxJ3ivg8T4Fe_5a%-V|ogHC-;2ZH{^ZJ8)9NQ-SbX6k-j=^$X@?cE>PMoVR_%h*dMk zXus|BHp@W=i$VH!uXkkT`A57|Pn2b^UjFQTvw@Q(h*K|%1e^OCMQuLmX{6+B+%0VR zrj2A-j|sfxQ{>Yc%6OzcMdF+uu7BNbXkQi_mkdtkeVF1}d|#2}2A%Yb@boLfgu5eD zoi6!eiKbI3)1=dyT;8W%yqt?8Xuh5XwIVIh%3Lh##o{@^*uAx`Z`N$?=|0%coXT{uMGaD%t%AO_Pl-Vst^ZDop96i58>iz-_uZc#!LI*Fmdy>`4+aoiN8xxg0a{ zP}92GUdqNhT!3j7cJI^Oa3yrMybr`1@64p3rx)S@p)*w6&q)wSDVEW`WZ9b2IDrti zxige5})9}5DZAGqB(+}z9KmGRw^BWWZFVFtH4 zGS{GiD;Edzc!Y@#hpBhJoF{xN?Z_OAR@N90jD4~2iv>RQQY_y)mU+K;PH0Gc)FLNT zeD%)NK*93r85$=uoO6U@z=1_P`ZZlGI=i$SVnZ$KcT#wY zy57E8fmgly289cb-bF4SohdHTi`}l1S<=UBuJ}Y8!pI@BBQ7x-TBP$#aAoEu$X99G z5&aM`g4_2KORvQw`Nlvr&&{XN581@=`dsAhb#b8I`5w_-HwzFROiK`juq!0FoP-uR{yPQadp5BE!T?zR%x{=mgRGAl3Atn-c zfJ6no@*_w;@*I`_&NEW%U7ho`XYxa^ac6)|5Rr6KEAHCPMkzmy zethfE!(MQC&r2J3e7remTjal)4qw3h_} zcVb=l>H`}YTe*)Q7t|S9)oWzM)%2ti)~t6U(l(^?z+g-wqi}ck;7tM!%+88s7f**e z{1d`$P=QJlVo|NKCS#^8K%i{j5>M6XH99Hk-Xv#rZN3DAaeZ!0U_7hMV>1oQDMLr5 z70oMh(FG~PHo9U8TVwxdgWo`2LRZPj)uf~*`0!SFC@F&M9k-6&6NXLC4)0&v1Y}a- zM;QoX>u}%H*?1wgG}tNu8q!Pdrxfms9xhvJR-s(D)aG7Ulzg2(rm;llPrfmqALwP2 z5_z6c1o$MkQC~uJJinoUILtu9^C3!P+G^B z?-6z!`g$*f7+?8+nH5fdi}R?T%3y_wo2mtDi`qbmsn%(gfAjsep+%}cH~*T8-1-syIwRY!_K#_{>5UUR|hU=MrNFrmv1rvq=dzv zJ^At6I>)Uv*D_r;p~}TiB2|w6jn#d2Glw-T@sUjvlY!525XP@BCm=q2nYz4re1Xkm zhu&Chk+|ehiDBfE5XZMF(>m!;TT`^n*M#Hl5B7ULl7s!_6?4e- z)!chRy=t7*PCH%VKS*b97dc9{%Eimx*bQ~7e5Cz^VT4itg{x#Efh@E#a64VxPCwir zu+19fcvcJIBtL>4k0UmohtIHBW!vcuzi7kJ)SWAu-_c=o%Oz?&+&t+zI*E@?--DBPAzeD72DQ3 z1f{)%B613M;OGhG6%6z7+_A8V#)svlZBJw2@I9T%p@xdwhpw_daC<+)4Yw-|0I&jy z3N^$@dOMWz7kM>WO6En&ESBR;w`FIReDhu(oo0LqzC0nj>BhIGZ^46Dl}giWqLpYL zYD5sCR^HD

pl!q%8u}%s22SqAHzvglfs3Qr+#Wn~HC%?ic;u&ZZKxk~H*y^4r}* zA|3IF7~jS52;t}-x8TY6>GLiWo(^hqZoq@{bdGNIGc zq$SYW42KxIutg2|3bd<%uI6Hfy5b;=6-SZ%8G5Yx}Z9Ry?()lOk$7hYcsos#48EBoH!aM zE1Z*z(KY;0bSbJM6`2ztX$;-k&>i^8ml1d}|F#qZ#{65NEjVMB?Pb?pR(LSR@b7ici1T zx`$_u<+l(A<+_rEEmwja@3z^SA(F1UC;KMkyn)SM4(n@xicCnm-TsCsJ{cK;&dqi8 z%ro^Iob&G6&LrbarB#_lnt;`(*s-v3mUos32jTOs_81lyKwz;vATal~&EI2lqYUwD zy;G#G)Zy}U>4B02e@YPC{`)Vd+O%ayl;R>gQU1>{z1+C<8@bvEZM@n95C#@6`h=(q zhqP5w$PSfh=0e}y`2@Pu)|N+(RX6E%0tZeVe0WTEdw_DwMpc>3|LKx4#cH2fY zO51G?X~Xvm=ueW2_@vJjc2+7>+22oDfG@MFaA_LDNl~T4p~$1fC%1hVPGQP9>46A%~@&;cfp|_1KqNqeVJD_T6d##vr z&04jN$g5Pk)!eC;_QBQGx$gHx0A6Ceq_^;-|rv z$Z+~VtF(&Vu#UMNVFdquW?|6%>X46wf3$d*x-qmL!X_f{o2zG!oqkee;uw{xS_FT7 z*`Kced03D5rPE-oJ-kFNJ7Q>4xvK%9Qf4*NgLfdBCUIy?gEKAcVm`%Y7WOQ9HR;(v zHr218K7-|I)@^%-o=1}&&mmt%;#zJ#qW0C0n(2J%Pv>S&b#ubBroz5-)Vry3+euSk zruiLdYBXGT6^)fL#RfI@MR~6zTG_q5fd)N?y&Kk@WpT8!`4*N&oNd@|yx%@s6YD85 z8HJv_a>!3#Aln#EA}S>m#(u%a86O;}oDGSx(i(;JEH@PDTnU4nsRBC=_&)u(W~gioyN2 zsY`-LLH{2dyy^fBq~q;8CUPa5{kJv-I@WamzjUnWglq>6o#q;Dir&{W4pecv0_*QX{P9TRhbx0hdQ#(*TFHt~@E116UmVJg8HS{`H! zhfBh>&+VkCfB5`$bMTMLzW(%b(fr?i{SSA6_PuZ>%CD9yLG2$X$vr0pG{3#4yZg81 zN74WVr)Z*hnElOA^<{Ja7*^=@x03BN07&+;2Q~2hCDvEdm+JTPACvr>e_?ij|5Z}C zxj%X(zp0Cf02Nw@q;|C|4mOa2dVvUdKqjSr&0v41d_9clc{fBz-_rx;o%ihn^{ zI~6$g^Y}7f_P_aG1^6HJM=Lo0``DkE^{GL{`6@E*~fyT+#^EAW20%a0$c&jA70yIu#T95Cns^+&#C0d%3=`Dx(V=U;*V z(cm!^Mz1gf1X5PniPs2jVFA})p~9+WQ35!z!q6VOM(_a-Ab3mt&?6N1T;xxue|Z4F zr?lEAJR}B``U&#b_8KTHT8c;y&_IkAS1h|KVIHbnSBiCctH*M;5a900iuV`USVI z5q!G`FnG87UP&Y|a84@lW07kFobmvHb+|S?^QEBCubTljV8mtAueAZ;=sXu6qyF=A zFcom@cvJ=5*HQpMf-UzCl4}Gj0E3xy%UMspL;~lGl`EvVM)3Ahaf0%nUcLeZy$Zf= zPZ)qTO~k5j0>a@GN&6N1=jU>Gz_EK!atc)#0D^Bq6wjls5x8FJ<)w=jm!$%5P9-MY zxN8L8FA0*fA1ecK%k1DU+Y7!#rP2qoOOViLcqsjee=S?KzI-F^CejS^Z-QY zm!JwNxbr3gAlQ!$GrSf#UeR6Z#ft7nG!=m0cw!aZYgc$B2oUJlaEuX?0D_jf9__&9 zm#8G>R0M?gF^nmmRB$LRD3h?|c8u2X#ROCXLEJ*#ixSUX_t89C{8H8^}RZF4S@`rjG`66VaYY!RI%J=%EWXUSTtgep|i8b!VV)C8u^t=g0x}|!4u1ho8u)k+z%}`$M3&=n@EKo zIkWxp2HF_&6~lS;pP>1{@MM_>eqmH=-ebMi){2NXch_JSRNBHh8Sd$vYK|8`0p`jc zxJ@yuxv-1cz18k{@1kCP7~Q-3l_o-q*%cZqKRSsuEzYMX%^=H~vRKGn*Lu0v3)S|N ztfudIj9yyz>Lm?w6!&DvV6rOXX%=S3zlWN&TA<4yR&e{w>WETB9fMB>e-cuc;#D~< zP{>E%b*zUFDCEuHmrcY(AWp=`i%nuuotg-yrl#;a*2gB;&CmJ_V7>Lr!}V@~AyxM% z$*-8B3J+jOpF}e37E--&k#Bk!<)(U^+Fmb%bL}|Orc7bI#$PA5spfiT?97z6z1Rr$g>N-M?!H3?mh?Pp{ zX+Ygsv}#Dx>^FWpOCI<)@BS2#y0uoqoPq)>^;FS&wY(-n&3<}s-@dg(1c&KYxRL{@ zq&mhIGd?LsM=#8sx=qt!<7%3dc@IgNUl7<~CEsE#=cx0~pCH8(N0S5#?;n^~;sZE6 zO-{afMgoqQ*3Q~pc{&EiWG2#t?D82C4x&f2&-sW5N;rjd(lbHm9bvSD;bliI&}v%3 zx|GKo$V8UKaWDU*RyJ5J{7Ks!yX5e#n^*^hx7miZ(o!E}v7 z20(>#So6Opi4=&R8}6hN_iAnMxk#=?zV6BF=reHZOF5EsY9yzo{#NZN zJ90{74?)hxl$nnVCRChc_$~g>(I^<7ycdG=UTKJ`Lkr{sXQdaQznUqQ3jwZwAz4Y) zq2!&MacHX-ma%GSh}-soakZH}t#?rCX$$nwhX9)u!RPlaLUA*=D0{{BD>B*d?&3?W zMyL7DUBwUbCy=k?2|5In85kbTbkIMpbD+&rPX&(^O_bHka)U=|MjABroT-ot-hd;kh;o+MFibrW=CpQWUF#!TX?VK!GFSt1Co6dJmt>>s^O){uG3<05bnjdbb zK^K~=xiWlq6uI^5zE*+1kvJIbOL(?T*~TgM5N!9~=55@UsCqU`H(}~=m*(fnW8*}F zv$AH0&Edurut4n4Gq>FULhzDoxl@0t7}jRGq*F^M=mUEG^3^o=IUp@FE*5$K ziILS%ueYUP_5)GQFESZ|+fhLpj7&w+i=3fWJ=|@2c;d6rY8lk|jCd5(sdIGAK&NO% zM)iZ#a^#R;0jcvrg~n6m@)%muAJVYa9(8i#8Cn0yp{!q?WaN@p9KY8d^N|T8)6i`0~htUBkC~y;>#9~n5mOYri@jhfoa=Ft)>$mgLvDE zcrlM(?tMn(L-30n+yRk!=T)-0(@d`vkWuaTt|8`-;g6~^c^c_p>PnmJs;WLZMh)0| zA1QArq^Eo*UN*LT-s8Q+vzb!-z>6cVz9bPW<5lR%W|>litfH4Wd#3j<1GtQb#DMG? zHy*8o_|gWg25jawdVudqM^b{O+_|Sig~PdL)6u{=%zD3UeZGX2w>~lob&ePp#75SH z%vQda`xR;uzW5_SQy7zq20bJ2SdrVfYLbfT?JVC?_9*z@j1l;!8pvrU$R2Q_2~Z-8 zS`@p}NKwq}ZnH<=&cins?cF3^O))?zI_d+_jAP1#qb{1}cJMYgYRUPV0s@$!JG+5R zVq=U|^Rhzd-do&Q)50Zln`T<&U%cU*S%Xr`k7H*&-b>(s!aoSDAS&211WcdZ)W2yQ ztwCYa+`8xQZP(5kFB7&=`MtK2R`99TmjlViFsIq7sp8Y!k&i-%v-bzD z5_FOSUeBO?Zf!+~e(Y!?`qY!DN}1t%tZ0Q%4~4r0=b(?5nnX121hnxdLgT&H-LtM% z2|!&UC-ED(!16@tN3k2shqdBF88UdC>uD@sg_?u?!w^@3Qmc2zUl%PDlb%1<6^x=p8ZGPGZ*@&h1N^ zioaKoP3;Ci1zg>XmbNMf+CaOY$)|VgHk>A&7@V6KJ_+wFtsDQb7oUm@x6SawxuKf4 z^kq4|y#W2VNc+T$uXUOVwZjL#;J;~7IneBP;z$KrwM&9;?!vZWqPpAU>~Bx>GI|}p zrsB^sdsmGhoev~5Q-jl3X-eMk+o>@6GAjER209(9g+Z4!)w_`)1hcIVrQO!}+B)N< zUbcr|E;Uv5C~oc9%FYKMS<}2{1*kmCb$R1u_F});1Le)$(1zTVC)A#s0U3%*mo=qcDh7Ym zz3dL;1)*%a-hzLGkOm;WFaf6WfTx|IA!Hk`ec=l}yYl{spL*i9v-(>No8@$(;v z76SPojQ#2#-L5*v4ic4AEg(52=Xyegy}^PgS>OHfN$1Owt!=BOB`+Me6zt{^2_0-i zYvC1}pd}AOw#q=4^TXODZ_o++0OJxCn7}H5KwJ8S9@#R{w_z&Xu)MFzSLnGZQ5pk6 zeAL@J;A>6UIzZNMdTN;qdF3f2r`8j&9s8Q|Xr8*uh>LP+c|hsM z+6TwaX@X{U>S($2O1eK%B0o;w3OY8BYair>XLo>>#1vru$YlB(FD%enTNhl76kTQ6l>JXG2wmVV%2w`4Drl?5 zNq6mQLP5>D{JK|bWpkVlVbFZ7#liiby&*MI4Yf{hcQtwM8tz`erI@vGNkR|M8xQqfM!td#+PI(&CWX@x~H!aE*`ZM(ei+JiRGN67oqpBWXe8KkUEW6_na4kw5&x2 zb;17x{j#P}Y>{G zI;5Vgb0IyR4R~5@bR;l=GO;MsC?kCgWG|ltg>T=Uwb`{*JM;NGMGg+fE8WO%Uz~4r z>`5#!+AEU;%$r_vaU^CydAU1|>zDaF`c5dn$24B0M&`_+iC-rV2Nzct{)B1RlM>hd z3cAd)mQw~NHSfmzFrn8=34=}MHL~ztA6q>I5vaFmYA#33kDY+PlTu?l!9uUi@&3>t9q32jC?F{yLj=bfD8IAas_lY=mYpIv zHjjkUb`l>k^KiQ^9-lNPRYxzCe~zXTOE|y#dgj*tbLtl4bgI*G1S323@BrSP!%WS+ zh~w?P-Ocd}RJJ&)M9|Gg;d4U8nKP$*^WKM1`2tp}FRaL{l#s-o5tfh~G`Q5M*w$nP zI*(CJo2*kydmnRn?ZUwVmin?jQpRr=HGed0w*@&I`GjKiruhI08$EnRz1H&ln7$jm z<=l~VxY?dE0qV5VbYnH6SOex(I*%M;%^P4-r?ILfZe)>_C9JgV7rBd5rw|l{!#Ua6 zZ$I8Yi(#y($smu3&mSIB<1mx*Gz@UyLChzriM&D=682l4Y)S&Nplh9bAN3a6PGsRv zJ5C2_L3xnx#R$IEeavS;>vNUty!9}DogPw6>4=4V4f$PDX_t1N<+x#sy(KiMy}pCV zCLh%>=$iN3$s((MjsJ~sJcdvg+t^Q&PhMNKi&khFE;F7=XR%)G_daKPwcoMedrlqR zgxWf!U_;e7$v&!ZYtYB$Z0TxadB^p!nHsPzM7DXq^~G}fyf zN46C3HlzMW;}my6Y4{Pfiti+cHqk{L(H~0bIt>ud1#P}`XW@eHA3fi-QS$UZzlewJ zjc5I&FZ1V5>-rxJa^|56IB^|XPDl*l#nRL>MN8Vbka4T1P?q%Gj&r@1!~ z@7FlbodbDrIG}1QliWwSO5*ULVR7@;fIDBry)Z9RiE{t0@87NH<<>OP`xd;Lof(j; z{i)OzeFdniI9cEhitzK3jX6>)O~w!#^$=2SOwmXWp$-ecIa&6nR4b*;d_?>B@#E@= zh7Y~E_LppVtj^s7U1G%HSu3L3$fg#JMx0~yl$(>2N2DIJt`ZfWiJR<%PzuiRq|Cs6 zM~pXr(IvJ6_H$xksM6NU*mw9wcz<{t?cSjqUy1{%oik)Ftr4h31iNq0SOR0=Bc3ZQ z2_h~uJ7DLICaKwfzRBm=M5&1(4zk*QB1bb*hV52LFzZ<7g%vLx5^)eO1~GJIK(Fl& zIB{`d*qM`|nt_km?s_G9%H@vfwQt~P1nG^&ZMvd~(fw@uy7M~hG-6yaTQ&P(dtV9S zNvWY#s7LB}$#97grTcc8(;JlVRP>eZBUt5HU+1st&7mA0p-Ryfy+H;gW2_8$Cy6lO zi8?-GWApnINdX|aRv9*98UFSxyV1mwBp}uQlxnnJF>fJ{wH%&`u)~lJ13_vW$j>JT z>5W?wGp+3{m{^C_n|i#dinj8l<*iut8y6O=^1`CpzQ{aXY);W&yF@6YGhXG!7r!` z_RfE7=zn(qWRNEpFB5Oo+@~uS5+8(G&N@NM-_Y`?7`>=?9aiF%&%AKNg`d8>Ylf7ssE$;KEdk6}$4 z{045hMJHQj>1FYx2zVt+NtMED59csljsR|IV&foTc%?mS?I}4&`i)lTp5DlV zmKSx=e(jLK;t_1$X17^jqo1J4?xwQ?s4!7IwRUmaYoTXXCr|h3o-NYoXHADi#LI3+ z)^M&qSzRo%i>rrJp#eH!4}{1noFYR(Tgipp>)0=*KHtElAWyoL+84Y~t!j&dSl9Z+ zeKl7lE)W9pRSDJ^9}S>e;?w<*=V~0gYbxR5YTv3t=%De=Y_jcE=Ll*LZ6-|Ks5^?T7N#N0uW@~13K}_k#oBzRzTdh zVcb_p+}e5pc>NBD^N-WdBnS_sJkqNP>hG)my!?{*2dCyE3O4l=GRSa+^ox@@ub#ag zMzghtx8a+yYV3bC@4sIP%v>d3Ejd`vEP(v*OiUrW7Sxv{0JwYobY4D)8N0EB{$Db9 z0~`^f^Xw~Yd{$N#Q!wkYmrAYUoHHbha(n~OtVhSkkiSlS8)%O!^B12ifMhvx4EFc; zou{ndD>^^9ApErJ0k9{FS$OaIjXXVps}aYV2i)++c#rL;5>Unk>_7OVUu~ZKPBK4T zPB(%pipx;q7fDDE!}o9AEh{MCP!jocZJLEjtb+ghnz_IUUcFf%0*0d0*do{F=S+5& z!sI6dZZ7xmjXW@*dCK$y?+-8%fjL8lKg)`)jkX_a0`k$QWSt8`ev220BAr?( zIgZ&BBApixJ7cG-=jHgr?Mc!Dh9-`m9NHQRO3QnqRZW73$||=113hg6m&0p~<@Y-s zpr7C0tuM67CiS%g;}(eXmnip6y~YW3BQ@5QJttU&7Vb5v6khgTM~h!U-vfVs+$?;; zfK<>L7x{zAd<`&F#{9j6e~8{dH8J!}{#29prMF7M)aft-7|~JfkR>+v!2d%@D$Ibc zU1-#B0H-ak&Vz$g2paqIHcVl^<;f2uP%||SF*=&kA{r(5rN{5|OxqhgHv1S4{sHRP zMuXZ`{z{<7eTt)YAVU+8NdHr2c9#g$kf+CmAp3*{%?Hx%x;(ddI(ED{LAcx#AHTII z8~+AcMq27i&kZr-^OrFA1M4G~W306ByA&Nj$W^af%ka~B@`NJ)$+&UJ%yze~o=I3D zkJ0$X*`;mimW2T35~j)ihmPe8F9*G&{5|(!m3Fw4yk8<11E1YrBQ>B}0(~J?&axfM z@MtLx1*N}M38Lm-x(v6#@Y(zTmXU9Hz%H?jTT#TEx%rK3Ctuk zYdS)kUi0eAnmZQh;Z8f1ytz71AfB-lxB`yQqr$X7p0PgRHPG7g(OAx#bofCG+pF}t zx95b5PwL0uuhyskiRDid(EH8t>9a6UT1vvu^V@LJh(=%~l808@m2o-BRwV$k8n-U% zPq^Z-X#ym;KN^qeLs#odDxQr$E;gyaF!4Q7Ha(fwZ*~{BxvHUDK!qeH2taAqsy_d4TYnD@%)+ulh+-SbsFqR~CM7dI9=QLgI-u{9+ zT^9EueEX%SPkIOo35l|<-qo~#2>j!62A{w87OGe9kLU3qM;wkYI`@6bBk(<)x zs*mOmD`1-Y?VrBfhTjG(s@jqZz5wd?UOkas`f#Ts^BYPl(i^SekRfP3#H*x^SN?*! zMRvc9b*Na;F%k-DzeTvB4Q^XPQG15`QfqcUH?JSq^%1!H$Kgoh_Njrg3icgF0&Qh_7ukGF-z?%PIs>( z)8az1hTIZq)y>4(=`a9!g9gmVy#h_Enu)LwKD@G-vKM*@_Cc?)_hIao&7kwS&nO!$vCPv!^r0H7wepTk927n7jCjxV2BtE5zG4H@ zAYE0;Y?J&_s;2Wm-8bg~#EXXy7J`z`&he-%YEVCvuTcaEGZdTFP#KR7egOkxu0Nzxo2@?=%lPy_eRiDj~Y`Y)|LQN=niOjSn(M>!NUWYrEDCAicGw z1(T(GTlk$#9RNM63B9WrObJbz*CfYu`WvunxBda_8zq<R{<054KT|kYL1#A;0G)}IaCf9%sVkK!; zs<2I7A}|Lw05XFX29w4U1sj?dUNkpY(*NBnBf`3bdr;w>(U?mXeBr7Oqbr`ZOX2N| z-|LWV&a3yOWkn-HjN(4YRW6eOR*OVu((`mQ-cngySv~Xsnh<8n<`Tvj_i;WXK>2ui zJ34&a_^WK$aX@o7S%_|+-b8-F)yuNY4E#DU06;D{TjSpxus{+JLW9sKH4Bjf(_APr zxV}=LSg$3tbqAd|G8iQc`SwQdaFG*`lykr~$o`wIfY$-fq1>Zx8zpxG*mP6}_DsMk zmqp{lhduwUY<;;bJ-O`)ORFxsB#E7+8ZmN@0biZlk(cmq{qXC7Tt{mLibn%*!Qa2- zq3{rpsJ6DmzgxXbiI+9_109c#w*gkG=sZ0PF>)>~oLn^h)_-duElog>M;Ha8z;4~| zeRd{o;EmUBMqK^{bp^l$qiRYa7Z1Q_{?GEFQ5GU{;EDz18@K*FmoHH6l(EaD^8(-g zz2g3wL`500dvea&m3ujbf0Gu}2~pXn=&tec4eUzHf6m1|q;>Kd*lNwf{&5+brBQ#m z6RWVIZQO#Nb~O3^yeZTWr~=O)7S_|#c^icOGpAMFG#`_cH|l=TnqH1d{(=Y1z6?+? zwVviN^jxmo&jsyp99j7Z4CNUdnWD!NPBwJL&$4a&G$^2bK;JAxqCyE7d~bs&Hms*R z?HBUu&ep!{T67TUw|pd{KiH3hI&qX)H(@CamU)vtxxn{ed;qiGiIThAc$>gCfAopb zY+ygj+6wE;-8skFirsji&;wr87Nb#5;QoRdanN9*s~jWeN(9_y_uzE#z_;Rs9jb4Y zLv&M9(+U+z^?jfz@*K@Skmo-Z0Yt$Kyn~v7a(^$b5d^gevvg`=>t15Bp)q~BLZX%!-(}H~ah7uL_Vz%FXDSjzwD(`M# zjNF46IT_eJz)9pbjSM_3xefO^jw(8iK|zseUF9~5FP-ya5#r*y-Hh-Jv@7gMm6M-W zCPC~mn2craUnT=GUn-x#_(5^COJDl%%n#+nU}ZeOZw0_?USgRt4oUTZvrV4SX|03i z>^5Vcvb^H$$YiK|^Tv|fke@K5(F0gv&3jN#6Dju=0|rkqEvJ$8J-YYgNzEQC->^lS4?1M2*)perNPeqrZQ@55*HERV>{Hvd z4^_~KgQAQC#e6;0EaqKSz|_=;3of_6*HHqcO7Oa@->ua=UutU!KsQF9tQPNE>=b_g zio%P(J`_BUjLljPS~tw9RAAb}7*U7uB}vkD`unrkDwoL4mBk5+p_LdVienJr7uqf0B50`8o)_O#4 zZdoNww48h(guQT3^l>7C5Rer=n($CyLSP)iqh+m3n2&ITR|0N9GS3cFrDHFfg6k-w z37YYN=G4Q|olo}(xfl8m%mXaOq}zmy+g|xoO4Mrf7k3Ol8Z-E&k;U8!CGAq`kbU&Q zgPvH08&$fMq}NDKRsBEwuBK_1kz76@BQa?&dcHT>vFL%An-LfI5W`i_;Zqcl)cjd+ zRu(Fsg56N%VrkA!IoqDEIKh#A8_k7n=#?4FWyetQ2mjpzSEy?Fnmt( z?CZHohpfojTi6Jq$5nSD zJ7b)CwqC42{Ol_qT;b@^|IM`Tq*?#5`N7NY{DC%`vd_#hQ=aPH^v-02S%zcHAp3UA z=EgqH%4Ht&x;qsWBZuQ72V%v*;qZ=luZ0&E{~x&S%xLY7FyIRAg!xswS(*nr&mwZF zP1Vd8-az>JtnGNSpu3~PVa1DW;C0$hLW)e`7&QC1vf*6kP-QX=FhO!VEoo|(HfD@^_q>ifsK=*+@2G4fC~t~jYpf@X zuJ%4Mmytf5nh*{DG>kDG5n3da0X*f`AqLtrZ>KREa$`+t5$Gt;!(!Kc{t6#4D4-e8;G=;2GCFtRqwMj#caK~Se& z0IVv@9bF<8xwR9HGgKxXWy*Bc04+ zd&*^1nTl$$-=;KKF+Qe!*}1;J^yqfwqbS9vIP~F3rN-#zyY0`Z)K7zNzyBl5rcWviZ+M-Q=I|EPMj+Pks*JIzqu6 z_ukvtW#Be3JLOe?IH_h5Xv8k?k%mZefgWusGwq3p{$>NVH%_h-`y#iekYr?HfYf7E~GXZMz3C^o3XjH4A(>P_U~ zdYJ|DIorsum@xIr2A;vHsC%qMPb>POO>(y>B=@DnO<+J7=W|iWFY_CpGSY>H_EMop zvrL|jG+mE)*eBIYEO*x>u#np?*XWnz;3}4^HN6)Req(Hm8K*UPTwTN>v+yzffNNK zB#M3%fm-K}Dc_C8ZU~WqlXQQ|*E!;h1Ql+r6k6hlxMo4c<26lsFHv^ztV-(7vZu3G z9YzX1?G5W9-IlZxynehY0~>d#C8hp8$Jy)hQe9=)AamG=zgFX|{*OXSunfX1kG{&U@Lb5aSg8akh?nc5JQ4TT~3^pP8iyuQsB|e0|!-%`QIsXoloa1au z$YF$UuH zLs$HOoSNQvHl49f*3P(}?CH3-I*MIPjf&XByb?+Z0C&7$?7}W1=Aq~0{mJyqNYeTsEJF`6N9l!Dt3%QRvcC+*>s095! zRlHGew^*UuRT3Z|IP0|z#|aBnvmt*YLmBt!5PGh2619L>7tcG(BSP0kfcEQD7JK~2 z(=Mv3`0;Qt7B`M2yikB0n&GjV>@m~f>tKt{XW{tG(>I*M`w|=%Zc@+=|xOlX>y80#ow_yP)T(?Y3lMcA#-TN~%Wz;AMkz(sx*k+68 zvwGGbhiJ~%tCMQh!a}4A*_`iPR(bpcQY+N-Nq*w`e!$%gA6U8LSTSuwD06dWMpD?g zGpag87U$`+eCdtt?Vm^ZAtUEAVki-@Dh!!Z&A~ifpnoH~-X~vT zF!ocYQhfK5f%SHzR~UukPZ*)@>CV=8WFUf2!yRcWfMlbiVL(oq3~lW^u>*nZ-QB$6f18O`~Y$%aQ1-{$xHWM-2I$azjLZ)|C_>gKRFoSSW8xH+UaI>L5)GYIOwb=Qx6$z z1U06e{@0&z3^UAEgf`}zC{u%=73d`67oq$#yOu5E*BU0T!<=Odn`Y>Hko|OiH7%hp zTDL+ziq-o)q-WS&xIsMVhDXHYd2UQ4Q@{Q}XcNx=3rGwDsxY58+OqR&#*ANxMG^4} z5Do0QXyxi-(=E^_q5T|G(YhI7 z`^7x4{?iLm&P%qpvF%X8TPd205kp^*dX@ckM+qSSb+_O{5W zy>Kdqp>nf2vhC79a_YFWGY%Xvk8bYts0co;=hU$|?=>4KbNTxoD1IrbP^60LV{cUN zgL$J45))d(CiHFelUo+N{hFvxb&>CNb}{45c)w#$yez3!5SbH-zEQ;!^Xz*ii2B^- zR!c+&wb;>En^_Ndk3^)=>1_ko?+;`SJs5}`GkgAG{?Her!qwB#Ey$jB#CzIeaO7YQ zwYG5aUrjRD`&Hf!sqpVdUi{EXAe^>aB8zS>M=3ZFVjn4;jRwDC(A9ugl zm_=}b603XvEk#Ae=?X%=vK5n6-Q|amT@@3s4?UAXWm9sVT zjx+TkUii&sd$!5X_V`Py2U>m&j54P;yE!y!3i{Mt=VPshE`sxR^Y$mX=^%R1tEUyP z)4^dqX1SYD{!D_0zv96osKq&aEOEACIP99`9Y5qob|j^|^TbqC_ZR&SVl`N@1q8h2 zgs(@noa#+g`P2Zcyg?!+!+iR1CBIoXzv}T{yp87(+zg{p#T^I;`lSoq_jb|Bjw|dm zFrkB^eROA>-~1)>^ZfvBcCXW(zN?QVi{(EAVMgkJl#1cB*=y@(4~2&gjH|Z065eskqIq|satPd%)XOG zr4~l3Q9he50hEE~2>-)YuU-H!ebzgw!ObUboej*dIixF{cozPuKKWv4J>u1))?g|M z(PlF*g>8v~0%qepB>`$i)LCBf9YoV|`ibfH3^A~$QlTZ2M9B1b;FxKasIq7~xXP!67#LeM%77Y~? z*$Ehb=Onym-t>%@E%?{#^ zUW2D62*0p%0Ut~|z*f!fTOTbYi>D1MEuHdLNbv3lJPO@|cahe&xV}00U&DmwfXb-g z7m>kgaE094iDVqdU!SYvR$7_(RJhl=9i$?+kheAS!`i-UzWa^;i#>_&kP{*1d46@N z)S>4RXYPK*EQFtje#o_sSiq?I18Ug(g(ATycodKPy_YU#&q?K-ca&Ra5id_fO-k+j zID1Cjr_9N#x@>GWS87naIGp}o-!1JKas->j;Z}th+qC*_V#X7T+c6^!8_kP6FL16$ z@&fR(iBO(}b}>|yG6m>~7}%1g9FZ`)J=4Gp`957297!Gbnj%cwCnxrLv1o{UubeaM zJFN%;ns@|lJoHD-#X$Y9y`_~NNO=6-)CJi|_k{DRqxX9G!oBlks-gmKFTSvS;PvzE zEwf0*p;^th-LAG2g>M6HzV>D)8!+ohR%+r?+U!5@)_UN-|ub-fc=<(U8-tUm(X^vh?KleN& zXOv?vYX9bY={}m;ImRn!KhVioMNRSfv)Kj7*Dkht)B0D(0%ny|Xi(}G@vL)8r62-j zwR7uv=dcs@#EdtfKQ?Cr{Lk17*V}$Pt*T{n{Zmn&J^zJpl@#La1^jLeYirBE+9!~! z{M^hR*H`s(OKVOY@gwcYN&r{DH3Ke35~%aG=9+UZ;O_CIb9gejkjNEyA@h|Et#$@{ z;9MH--OU}DnW6)UVj|}And`*bdy=o84iVyf8cWJ|cs4^mh;0XkBnjkwo|ET2z1*EZ z;@6uo-15;A4I9oWPqWSxoY53RgXGUb++{l?O4Z;fHQtjEru*wMV`Fz0%MpF}bgV&j z#PE@4U6+CaOd&F0E(q2-CLRIz<4VY@$RD+4>!s(I=M$3X8{Uee)y6*!*YtUDrb(mj z-iEkdJ9($@uF|~?F?m5_fv%UNKHi!N2S2~Q_^`4N%kZX`nyrrEPKI^T zaAB_x^6`t5ZD+PdHbMewM{WEu<7u{P$@5pONg73Cn4Z(YIVG`GUuFb~uO^HLo@R(J zOZgn!PGNJC=E~b=$q(J@pVR8pPmiABWM3=<9mlGe40>+2FOtYP1yV8l5XvXo1m%m` zPdJR*-b{Y_vHZ-%gci9sMm9upMnQapI^PHt*TY(r!4Q6ZW}jw(hLJ+g#9imnHecL( za4deodG9;FJYnW;#>e{%ZJzvneOWU#k>cECJ8$kq@UZ#$nVk8{^``gL zog{lp(6GjGcX<%I0VmoskTFy;uo2kSBLd2QzEi*adcjA-XLr#XqzYnTIWZyT-v9^{LMpryC^?8>G(GisY}>1_n=# zcq(?vSq|)bZ?f7!y&zmn9!ssz+S4Lr=W&=T6i?@|Z#wo2j+iuENUmnqVKq}f^&CAC?=GK+_m3_K!bCp*;hz@S{ zdb{DNHDX&cS0}J)*Zk=Y`2E)2sVT)`OW6*t&DU}3)WdLYE)$Gh244R9N`>lDx;NK5I>D z#YKYSGl1XXFY50WtK!DCJ8H0%IsGUaderZTe!u>jE7-Ps>(#CKe)BTZCv+09| zi7?Y*yJdg~iB5I5c0rx$nO$#F@Oq2WW~)?A?p}Ykr|KoA_$kneH#CI|2PHcEgj~W1xer`Ld+q zNCMT(7ww6QCo<~ZQ{7MEElcCBj${a7*5jX1?A1uU)L7o>hKF( z$KsKV$V4hbH6{u{-SgJ-SEp}u(6TIVE_|aGr1+_6WmR4mW{=n>4F}aZXs8r zxGG+I%Ky?jxPNSCYE>>u7^!+WZ0&&6QqyFr-n(+I>br}dPa|lg`{K!#t{II zx=+)pFe`+RKywa)I;C)awASWckW#Xel|x&7vmaODW8*5Vu1+AtRy@}1V23HLtZ(l~ zx&DU>ppuLf!7V?5a?N-m%2T+hV^wKB=Q&n&7 z9Bx+zwqLVhn<+{#DiyEe!?kYEbIOhg&eNEg6UaqLXI?Yhe7!b%+Le|nk~JdbJhkkj z({*OCLh;S;^)LtmS`>tUww}kUAfS?Fd#~!Yh2L4TQWEFa6!3-*iuRK>WI@Sls4V1~ zE7#_YTLMasuSf-KjU1a7VHwiOZ{eonzKTDk29ItZ*D=EvBWfSOkJ$q+1vS{H8{0rn z(Z0xUXF;2a6xiGRJoWJ51DC1yb+kAq1qFJ)I%L|wZ1c#iu*(tT9eJ?969(696sgFN zX0L+`S2aIh%vcb4$9<*cozd>o%3POnG3>Wa8`70IT1OEm7Hd%9c!Cr6(Ebx=Hm<&O z2Jfo-h?glYq8_caI^3#L$iO$Y(3rz+YP`EeEcN7l@NCeX*o+!ClD)(dM0%3uvmig8 zd=OdamO4{`^x-AalP8t$-M$^LvnE$W z+&*wzxAG8=Pkc7sQMGa7#+6D>|NW`qYFBH8{oyUH9j#kx|s?$6zDlaf(JsLi5hR<3d0d1td5Qk9y{hqan(Q7BGp*BOzEx$r zvGYc;WG+*LODpOU7qz@%PjP^qWtytgoSoy0RC~9Zu`G}C10{EK-*j-M#*vOdzAndB zU&V$=tn5IZlP%ieGAqIULk%QrsZH=6aiPRJEV9tvB&iT$M;{+GWD4vskAv9=DI(X>`JiSZ@k|OTVT3Dyn zry|*Q8A@WtB*rL?^{t$#AdSfW_I@wFP$RT>eB8O(T)T8MB{@&nQ-07Kb<}*wNjf-X z&x!gRxdJJk7f~P6vnn?5wJy~-t!>M}t!4jSJE?7<_Ino1cQw9Ua#Tt7RFaRpIT2@= zVb3Jjb8=yNW3AF&tx`KuW4y49Gi&@=1@tSDcyMD>6Fn^91COjOFB^9{Qm-t@@Y5V> z2+i9Yw3@tAYF~N(YtcsH^}e|A_sEJ1wxxI-m=(43{%d<;>!8Qn!;r{K@t%Xug$=qE zJEbqwuSP|)_41`V#gjvsj~J`6`LeIwIi#N$b;?HVO7ho`6i;$c|a7*&|= zXi{2PoKdzD4p)v6INIg(Rn(?wlkN{T8(sM%UY<1=L&#e?q1_#Bb*C?wA~EV~jNb~=qmDZhr|rLAJ|WheqC$?wZjZoWJUCnwBYV% zc}z;|8oFneZG@$;=flQen*24~V#@;2J;oo9y zXL>G}jGJu`gFV@hj4EBQUML;ip(ZhnNlb<#R;JeFTOC%E=vQBAI=mY^`tfEZE>{9K z;p~a%H8Xm>e2^k%|1d_xWYCkjlKt?#ye9dhcH}lzmg~ls1qBJXK}_Oazm$OtZg|=# z%B1guFzJfG11++HDU@-GiO`81SQcu_RsKZz6zUr-!SGfm_EtVui!-L8P-RTEbIRy# z@eTEp>4*}qIw-q9s759-%GqFWym9kLXL4`4J#laT#~0gEY$6my<8BwX6(Ex$A#A9b z28NmRMv6uRXCEsbh0nwX6Tb~k?#)#iI&RHJd;CaR<}$W*ALNqOtXvJ0mNUcI4dbEE z3Impll%$iqyoMZj=*7)xR7?=OpA5I^G)hTq$ah8#bx3$x-Q4v=jDp8rr6us@+#ksa zS<5?>gR<;5AC&es?pCzjj`0}FEEOrq8cWZKI~pGRbae1I2!5%G`o;@9&Rs^59%t=y9alZ@#xWg+VrqjmMP z&7p|Rz)r&v;``vkIJ(M3j5+e?REfuIgYX)3d)%Z@M@oZy2ygH3Rz2eudcY^y{y9p3 z<%?p?e*RQv%B1(t|704Wfo+O5i$<@cLHP(6K1UB38KQ`iBH%m_&FB$3A+{PJZnS(`~LJs z^O3!U(5+zHk*`v{M&}q>U@Bd15StzV5E|ZyD(2WeIw%W#mqQ;w)cc zm1i!O3g1^%G^v#IEnnZtNNzc4i^kL%RuPkmfwiEftlPW`%IJ8>>xGxkwtJt$hI;OW zD`4#(8EVALQZk_yAC2et21v<2_)K4idQi6t<^2mY?B*W|OBrHH;+$~vALTXo8y+~@ zbI7-I3RY|DH240#Y$p1|!K|<3s)0TkIc(bgjSwtw=pu1R{cJGNUvA)2dP5(FwezK) zN;4W!CF2dmu9_j}_#UtlNN85tqXp4N@`dMnHNuRA|&Ct8u%(eVcqP0gB z(wRre5kB-LNB`TgXZXR1KdONguCdfj866Zc*Bs5JSk4dUAYZlc_EEeW(HKq7aUwf09WnDxvTOHTREMe9v`NSWH_ zhY9uVBv-Ci*l!5#RNIzLdbmnF|FA9IgS&W}D}9^(w#?ThykvLoyeH%1QM>Hi)dbhB zOJz1e@Ve&)9MgK3Vb)n-P~)xr%ML3HC59n>Z@Ul8!W>j#0k&;Ioie#6zAbddTQe(Q zljHd>cPvGRMdYq}%8WDnMQ#y)W@o9cxCqGDzf#9+*fJTdkP%tQ@);4_YveFA&NbiVIUpxki9sk;?1X<{K0sBh&8vK1X4bM96QnTT-5LO)mg;7juS zo3D&Mc@V;Vtz^uDps%xmB^h;)L%3H|R7|iT;(WF~Xx}dcId5!w@4!Gp+~X}!O(;IT zP;x>YdFUNH#mUT)@MGNJ3?eEjdlTfp zPQ$l5Wh&_fUs{5HOX)?+lBZK>36!i>CW@cyh~cFwTk}rieqL#rD{oG9nu%NA=^LP{<%)&umSpxHc%yiy9+~^z`}!@|Gq148z2R=I z-4ZeetuZo`>O)1h<7@Ek$*W0F*6^5k+Ps@tjhcNeH<8{YyK<#+cgSdxEkV!J+j3){ z8^+|fAcrpetP;9sMIQw39&KHK{T%kbfW=*i)n%$9%8&cw)=VHgQu~|s)3pKj^^_Xd zVV2!zwHmLNL6Ov^iyQ|IrP}vaEDuTIuUWk4Op+g`ByyN~)ybb2?w&Fp7&dl|bS&zW z_Nb>7Dr3Ga72mJzChz9ERx<1W>bD-P7h>30$#HS1iXV8fH`eW@vN?QEJ;}NmlY>^_ zdOvkn^0{@f#KzHJhxT51bRHUjG-G0@e?c-vm7EZH=ApGjms892mD{40g`#9>{Iy5I zD2YrfoLI-wGYK|F8Dv|Co7CWN3N?u{3Q=%lLkKs3$)JLFlDJ^*52icaiY$>A8yIMT z!;9K^jyz*>9XFU9WKmsR_bY#d2^*(9H;~?Zw?3uPY@`1P>bHfZ+DOH zqlmD5-yk@%=7$Y|Ah`d=3@KCK23R;&vF8kquuRkk9;={8?X6*D?_fu+Ojlc#SMW;@IUP`O;jZ?fY!U)qXgb&?XAo)l3sP}WA6c#K*~?u+e(TL|E~ITlv$=ha|M4{*{rRQXZ& zvx35RkG2tu4qZ+bXNJaH$9-^cp-9QW;zGUv#ht(lIj+;q!RRwrwNLvtl*P^PNXiu_0G+o=`d&F^KuS$FTMqlE2|oni3U!Ex$=z3+=Dta>cl; zCxifMOWiM+dDojru7fHesF&Nqn2uttlL`R}z7LEQ*3=|TqwWi}ZIFUI!A*!ZwFw=S zoOH-n7q#{M4lgl4G>f58YnC_3{qUm}!nhQPtPmQrYk~t}Nu`biC46ve<6FC^(@(hk zIbbG>NDAU&m*pp;*IVR}oY|d~@a>ejJ8~=Q()R5>E;Ti`zLFA8a+ik^$Cl|Tad5S9 z?8BWk=9M^exyEzHkl!AFV)Se|jSYf#HO6xK)EJk%`@S0bNzLu^+{~gJKtPv@z180j z4QeufO%8(dzrA|YFX=b61*in$BBc|LjT{O?xY&%@PjOa5V8#CKo&!6xlDiwelYbp3 z@M9PDd3Z276!5siU&tn_LsNgh8GhUO+q~2uWPifd5s(@7euRsSfg5Ar%H^!=oWeru z2hx;q##PTBuY^wGx820{VpI!3JX_JXWqQxW&kYbW`hYr6yZY@aKYZ;u8y z_6BTKR*T7BFg%ik-%4JkD1*;=wn-JWNy%(rry`sTIyI6EurSupymK|)0@1JBKo+~J7{2m9eXcZfX;BsW{Zd1__QPBHbz zvWk{v0UIiy%kuE#9JRlGia!-GYRF^U))P54hFlHuO}FSs8zoM^xEud;>d=6+Ufe>C zhAo(6KOf)yZryAx(JEi;?np}1yCa3LM;{(uzqbjtuVNi?VD;yGU3v|h+ImKA2Sc- zo~PQW0gNUSB5}JLg6=f?qFM)?`@&52wfioN_j1gIw*3=zkmT%$X-y3CV&Ag%l{yH+ z9AoJWB{4lcUEyexeO)ilcqyn_;rmB1s1x0eA=~_*ahI=tvUu@p85m-y|L}17Joc8W z>Cx2XI@I8l{|aM#Mdv9ehFHHCRMnW}Fr+WZG7&bP`$T&?U#yQqlh-_SgLAF;)q(7$ zEJe_%m*NE!FbC5Ee}%G+;%*<30zZw?PNR=*Qin$(vFY3W6&0Fm6_(L3JDgqoRiC3( z5~V{MpT{L;#uaWy440O(yjBaF_dG}+M53B5X$g}!_ewU#nEKdmc*fnc5U@3If14m_ ztMw+7CHq^dAMXh~c?BB{fdW{rND%yVfr3+Nb#=822B&{Tl|Az~I+?$v+-y9^-Y*#y zrr+XP?3P;(v?F9V=vbE~NMF7phzwTjrF9{0?SHU6PlR`Aj>HW)4HrY)?=TbWXs>$c zrzIlck&)ayonKSq>b|C>HI_UX;A@IK1zYH~4mjL9=5u&R;X&jycjh30Q-&86Uqs&m z+sQi<1OaML51;0xx-zcF#}{}Jf+3gv!WhQLkL^x~FKFkn5yrAeOHZSj@AKFqCOjrt z#weX%k+EEqCtZgU5Zaw)TXk_?st-*2bdfol#LPN4zU3}%b-)go(&g;jJveXB<)?sU zmB&@9fLmo95Dfb^@q2C2|ChBN1fgn};JAIqJ#f=+cT`do8+WuJQC?T$TQ3;6`<}5q zX^oHN3qUcSd*?j-O|j?43yvB_rJ3RV=?#}x1J<$~(RWlo>;+%A{HpJ7*+2h7_73EM z0Te0tTIR(!%Cv;BmauszIov|--b@^x`f%Grx@462^_H?wpONUcNNyUxA@AfuDvUBp9h;I+6r|5@hwD^8QT0$L!X=*py6ZnPzOD-CU z^DMwN6+_t!F~cvW3=;~d3Hj~wRfys*f|a6A?zoM8@1>P9M=MEtZ|}N3;?O#uubTXU zvJ4)*H99v^fAZxP>@Q#-S6D#+BsDRlAYL^ar6MDx{R?=!o^v6b_y$=jWH1RC7o7|7 zN0wr;OW-fz)^|gjPPWNo;e^*pq^(V*BPbJ2|Hu>l2aWqaX5B0;zc!_Beg0+LQT>?b z@dQWoV1ng8Cg81uXee?>0(8&|=jsQ+uXimOc@qv6G_;4#0@h(QyaEty96)XQnz*z$s zKQ{D2rw0p-9>^)mrW|r5DN}wMOL|*%6O>~YFoT>K85y1Oy2HZycoM#plt?8D+s-zg z#0!|0ZooEtJpc)Cygr0}l4Wx!PSxuA+fS zZX^bUx2#-D38%Z9p>OI3^2D*{w5+Ruadz4zhFNxzPwvM{fmLDXL0iB|lX=MT9%cQ5 z;QD>Kd+)1Q;I`B^MF!eIEfJ(`dJglhR_L8SNeg`bw+reYcaThr%gf)Q6b@UOBFhL)L>33FgG|z=WT43yq)Quzi z71Bsev?<_0aK)HYz=w~JB0pokD&m_Vgx=S(%NHwGzqPMIiH9*qb7JtqSPAQjoHDu^B&@??0GJ{LZRbHcY^TLW;p5uvVS|bE+a?@62f6>1375boIvjZ z_U|yeMZboOyCno{!wCTrR_MO#19c#MC2nn>atdO>mr*9N8DQyj!|}P%>59LO28zRM zAe-RrNgK@2x5GC)=&E8I#0r-Yrl}z=Qd@L$Q8%|<(jWP-B(wMc8rm3urPFe1KBrHJ z10`;$Kch15D(axqc{Q(c{h89elz zKU@Ln^!1-_R92^hnLR#3s&jf%Z;fL&XH6u$*7`U6_vLVXF*Q$I7rIK0&uNszQndfL zTEuB{#L=qtWW{k3KE5C`<<&sB*ci@HIg9U&@-MDCO%AkH?pyE~llPI@M={wDPPy#x z+JO1o&VwI-*Civh5%bn=a@R1w3Cc{ib5+?wsAaKa*wmF-E(s7zhuf6vpmj&@)J2!p zw7#sfw?64pAb@s}dk;U#YRXc{`<;<>`|67ocVB+AkUqRJ6ViofZ zWLWqAC0kVtGr4rQqcLoE@K@%?xgG^uwLk*OAIAsyZZ>oj5VswEe19c+Q@ACY%9zwN zm}vWuvD%Q%#(^d`AKYrZI2FBL-VN)Ly0afwEWB!usTIyEs}C_6{3!h0t{fbiO>A@} zyu`4+mh#|4uzoHWxfWdlXu0D6+w&D%XI(q4?S(_}^-OZBSM_}Jj(Tgd=1tD&mn{3# zex5({f%$0rQ*MD{MZV{1AV|+$tZUx`qX*kHHD?&L8cWR$x=OUk}9jEQW+kjGx#KjxREY<}d>%ZLc z>EX8Ng*IkdG zxG`tIO3s^80W}QdF{iE)_Q;+_{>lsdOVPJBK5aGLCo_Bt?0b1#i zd$d=IT3egVCoE)@>u%!*3iAAMoI9YMhdmS*x6+<5V)jhqd;uvo7a^CKNq? zEJYD^kFTxvZ7|FJfvj`fO(~0EGhe&)yt>)quY&mE z{`;<}1p9|X{}3m}tPEn70nD!7e>Yzb>b`3|%olJNKeVZ~JFMt4MYGQtVmeE<=o#Cl zg}{mVZ-&FvzU$fOW>YZ!ScqKCKY`gbQzl=?6styVbe>IK+AW?EjCbg}rN&IT<1z~} zoito^+>aIWz_{UsXC0=))cJDX0Rh&znHOHg6%Cyfsv4y$W?Vyq^l0w&ozL>HN5laD z7Ns6?T6fzXETzxl3|Qq`33^N5_wySrb1ak>p^GoE26J7x)@}ks!GgIn4RPfd{eI_t zZcR;{M(VPA>Q*MUC47d!esJkxIQPrtU-|3SANZ##X4HUZh~0^dj3jqbO6{|cAdx?N z+oa0Sk> zc&tpZA*7U3pdpXV((-cKk{8akM}@Pbre(;nmNj~o?xDic?|UvB-rm`{$lJLasUxgw zxIHGdPHNyO9_v|N#X?Gva^maP9JkzpI4VZu=Z4VH=Z=B~CMK{u)bMp^ud#hv)!_9i zaC9d%F*m1xOHmK88HZfq+{J;Q=%#A(KqPYBWHChyd6*G*m+_NMnCA2T$Vc&~8|&Tt zAAY@6*|{^n+PltZ)UA_j9E4IJmm3?;O-mac$}?~j>Five(eRY1gACih*gf&mFgYcq zuf)#Y!S0(y)J16TIyIfkC3-NMe5$Dmh#+hH5ljXx|&;B>Nu;QY@3Ox|5 zgCYXgU9Y}%352}8w{su8h!DJREhRZO zaRUE!gWnh_$WC62nHE#nXfN7#Rs6yhPC|B$)~oYS{l-p4C=mTQ6JyKb*uM{p#mPY^ zwJ%8!xIdq7u6JekjfI4u_VlPHGD`Xnh6qBS z;tni{k`zUm;seg%M+)y(+UPH1?Zd8WxXvmo1Adx++SO6($C^rbrH6QL^_Cu~^ zD+Bj=t?(pS;R!x$^ZcU%0UEvl5;B?((Ins))zAF|qc;lPE%S6WEG;f(bEAmUGe@WiQ;Q{xpqX7u4d;9Ckqn&G@B6+1vzY9G!x+l>1No`H zFuZSGva`HAo?JI0I8h1gl0`AufdU{k(Jly*dI(OTyO%yr^S%%tSFDAQ2BF{|KRgHf zIi7Wto&{f2+GeY9d;yR_FW4PTOYpAW6sO=n$%nl~(QS3&_^DYgx{`v-o`d9fim4h2 zE-!u^purk-T33EgMdR@3ST>Z(|2p_8Vwh<@KK?zAV!$+l|3Po=Up)24e-`Qzdq(4^ z;lVFBDPs1!-@W@KWfny~UsAh&Z7h|B;A)Ms)fRl+?i%HNhKvSpz0Mzh-S*qyu#CQ? zWty{gCO^Q=Z&~jMfn^uR_);@|8A^zoV0;E^6x%IMyEDgPX_V?>fG;1BSqEfEkvW?45EF5$QyFblTb5 zIwAA&L|4`Yv7t}%M%A9uKYH}Y(A~X6N?K|(xlRRKShm`&EE_wb2;d_5?`}5%9>1iHmy#Mq!8=HTnn@0fjB~xACE)n{j`f7DHk>;l z&(_mK=%gq{=pF4o@d^#!o5TRSz3j``6XYZz{&u(`R|}E9C%$MLFbyp;Z#r2ujUF1Z zDzffIf&_9TPD&n@qT^~97H*d}5bBPZu9Nn|y2MM`z8f5lxy3sK{7>c#8duJiZ}FNo zkdLy;>gYLvFZvSK-y80uJa(w8rfW8!^eZJJGrl~SJqaHas`wC$IxLWuk{W$;PGthV z()NYfzMKNSv8Jp6SCnB26QL(P+=*$lo#*=vtY7f2?f)E*FW5^*%sSY>9R3w!vu!gR zQIgeWXK!z!fl@}<_Zd>b^xH+S)L7(ao4+6Im)I}{0n-~2Hg-=M?fRzUSP1b=Dy zF9CqV)@oyAxDS^3mCRNr3tj+1x%wZN{`#98@HY#6inY|hG?`zr0hjd~!^d9+(g1{* zr}aEz<-^8|pIPww+m3!2B@Zyl%>>~+$tJLy(}=SkjQ@dwB1xb)2N-ccSv!LR)9*LG z)%x4Aez}KQI9Q9A{z3l*7%6^p>8$u4YpU`qV#MCDHaayK0WfWBH%y)T4`6{6bb%4i z6mVxNoyjQR=!=(V{<|hAh2JSYERNys)45Avitg>Mhkpp|0RtHM_`3S4#VSyGAw+eR z?~g?-g~5n%?e0Jv7`m_h{@Xvcs|mJ?i2I;}Bzs6n{6@J@`-d15`S!@b?a zbKb}*^@0ePY^lG!?vITJDDr^yZ}xcI@dL|m%FC<*7xGK~zdnu?71flOOLhJq{_2;s zl5M~URo~^2&3oXP^XUIXj$(2?@QT7IUx2^8;4XXP^q*g1lmM*A`BlH)0)j-i)+POS zJy1-#35IN5XI>4_1hXReE>Zm{$-8R6-o!KBk-r7gul(A^|9mV0SuKQJyad#LC-FYh zKgRj701X7(KCyWmdlrlfg#LI&J{D#$t`Z%%6%t z1Piji?Bahu4uPjC=f8UwY)UKReej>-NO{3?DDY|h<#+r%H#k;l|CH-@74Ya$+qI@x zFzY_;yT^a(HTlW0)Lbv$V4(%mGyVA$Nnp*ftY5wZaVTbB>8IJBU;f8bPVj=~@#~`W z1*yz`pK}w4pn3LY0fz#Z6&Cd6A1b+c1`KIUxXVQGr?kQ3fmoVHZU8^c)}AA*e=6~} zgU|+=%n-Ev6Hv!%vPgjGI_}d{g1giI?g0E2fB@TvNL`};_cEWrrYt;^N4WpJ2A&OQ zF7uATKc)e*e!mAKSj1Yt{wH{jSDVfNO4QV7Q~n=d{8ksA6#)9DL>3R~f2~al0XX)z z(>nFft&)&k1Ky>1^?8BZzt_h8FUEMh+RIpwZCD9CpZo_9z!TN}ZK)yu5{F_3(DXIA zbE{1MUfUla>uBK1m4A!lu^=$WQCf4)KNWwxHs=4T;!j!qUse1QYSS5tjo@~Be9b!W z!@p5TLiWG9#pCFP6$S1o@dMePf#ny{qX5zs7J}se52Sm7n6&YQ?d3n04^aGH-Qcn9 z)_`0aCX}Z5r}mC5_Q!GP=`J_Z{6CNm4jUVCmnixF8-9QN#`3?qy Date: Thu, 15 Sep 2022 16:17:24 +0200 Subject: [PATCH 12/41] more info in the readme (org. policy and other) --- .../wordpress/cloudrun/README.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/README.md b/blueprints/third-party-solutions/wordpress/cloudrun/README.md index b0f91059..6b1c9803 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/README.md +++ b/blueprints/third-party-solutions/wordpress/cloudrun/README.md @@ -62,13 +62,15 @@ docker tag bitnami/wordpress gcr.io/MY_PROJECT/wordpress docker push gcr.io/MY_PROJECT/wordpress ``` -** Important : please note this example architecture is built for this particular bitnami image, if you decide to use another one this example might not work.** +**Not**: This example has been built for this particular Docker image. If you decide to use another one, this example might not work (or you can edit the variables in the Terraform files). #### Step 2: Deploy resources Once you have the required information, head back to the Cloud Shell editor. Make sure you’re in the directory of this tutorial (where this README is in). -Configure the Terraform variables in your terraform.tfvars file. See [terraform.tfvars.sample](terraform.tfvars.sample) as starting point - just copy it to `terraform.tfvars` and edit the latter. +Configure the Terraform variables in your terraform.tfvars file. See [terraform.tfvars.sample](terraform.tfvars.sample) as starting point - just copy it to `terraform.tfvars` and edit the latter. See the variables documentation below. + +**Note**: If you have the [domain restriction org. policy](https://cloud.google.com/resource-manager/docs/organization-policy/restricting-domains) on your organization, you have to edit the `cloud_run_invoker` variable and give it a value that will be accepted in accordance to your policy. Initialize your Terraform environment and deploy the resources: @@ -78,6 +80,14 @@ terraform apply ``` The resource creation will take a few minutes. +**Note**: you might get the following error (similar): +``` {shell} +│ Error: resource is in failed state "Ready:False", message: Revision '...' is not ready and cannot serve traffic.│ +``` +You might try to reapply at this point, the Cloud Run service just needs several minutes. + +#### Step 3: Use the created resources + Upon completion, you will see the output with the values for the Cloud Run service and the user and password to access the `/admin` part of the website. You can also view it later with: ``` {shell} terraform output @@ -85,8 +95,7 @@ terraform output terraform output cloud_run_service ``` - -#### Clean up your environment +### Cleaning up your environment The easiest way to remove all the deployed resources is to run the following command in Cloud Shell: From 0b5b9c57d13c791e4b3856d2068ae8d9c7597d82 Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Tue, 20 Sep 2022 07:55:06 +0000 Subject: [PATCH 13/41] comments on separate lines --- .../wordpress/cloudrun/main.tf | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/main.tf b/blueprints/third-party-solutions/wordpress/cloudrun/main.tf index 6c263c4c..72d50a86 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/main.tf +++ b/blueprints/third-party-solutions/wordpress/cloudrun/main.tf @@ -14,6 +14,7 @@ * limitations under the License. */ + locals { prefix = var.prefix == null ? "" : "${var.prefix}-" all_principals_iam = [ @@ -41,7 +42,8 @@ locals { } -module "project" { # either create a project or set up the given one +# either create a project or set up the given one +module "project" { source = "../../../../modules/project" name = var.project_id parent = try(var.project_create.parent, null) @@ -61,11 +63,14 @@ module "project" { # either create a project or set up the given one ] } + resource "random_password" "wp_password" { length = 8 } -module "cloud_run" { # create the Cloud Run service + +# create the Cloud Run service +module "cloud_run" { source = "../../../../modules/cloud-run" project_id = module.project.project_id name = "${local.prefix}cr-wordpress" @@ -82,7 +87,8 @@ module "cloud_run" { # create the Cloud Run service command = null args = null env_from = null - env = { # set up the database connection + # set up the database connection + env = { "APACHE_HTTP_PORT_NUMBER" : var.wordpress_port "WORDPRESS_DATABASE_HOST" : module.cloudsql.ip "WORDPRESS_DATABASE_NAME" : local.cloud_sql_conf.db @@ -108,11 +114,13 @@ module "cloud_run" { # create the Cloud Run service # connect to CloudSQL cloudsql_instances = [module.cloudsql.connection_name] vpcaccess_connector = null - vpcaccess_egress = "all-traffic" # allow all traffic + # allow all traffic + vpcaccess_egress = "all-traffic" } ingress_settings = "all" - vpc_connector_create = { # create a VPC connector for the ClouSQL VPC + # create a VPC connector for the ClouSQL VPC + vpc_connector_create = { ip_cidr_range = var.connector_cidr name = "${local.prefix}wp-connector" vpc_self_link = module.vpc.self_link @@ -120,7 +128,8 @@ module "cloud_run" { # create the Cloud Run service } -module "vpc" { # create a VPC for CloudSQL +# create a VPC for CloudSQL +module "vpc" { source = "../../../../modules/net-vpc" project_id = module.project.project_id name = "${local.prefix}sql-vpc" @@ -133,7 +142,8 @@ module "vpc" { # create a VPC for CloudSQL } ] - psa_config = { # Private Service Access + # Private Service Access + psa_config = { ranges = { cloud-sql = var.psa_cidr } @@ -142,7 +152,8 @@ module "vpc" { # create a VPC for CloudSQL } -module "firewall" { # set up firewall for CloudSQL +# set up firewall for CloudSQL +module "firewall" { source = "../../../../modules/net-vpc-firewall" project_id = module.project.project_id network = module.vpc.name @@ -150,7 +161,8 @@ module "firewall" { # set up firewall for CloudSQL } -module "cloudsql" { # Set up CloudSQL +# Set up CloudSQL +module "cloudsql" { source = "../../../../modules/cloudsql-instance" project_id = module.project.project_id network = module.vpc.self_link From 9c8969cc65d0374f193007820345eeb3d3b3f627 Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Tue, 20 Sep 2022 09:27:06 +0000 Subject: [PATCH 14/41] one block for code instead of three --- .../third-party-solutions/wordpress/cloudrun/README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/README.md b/blueprints/third-party-solutions/wordpress/cloudrun/README.md index 6b1c9803..6aba6774 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/README.md +++ b/blueprints/third-party-solutions/wordpress/cloudrun/README.md @@ -52,13 +52,7 @@ Make sure that the Google Container Registry API is enabled and run the followin ``` {shell} docker pull bitnami/wordpress -``` - -```{shell} docker tag bitnami/wordpress gcr.io/MY_PROJECT/wordpress -``` -```{shell - docker push gcr.io/MY_PROJECT/wordpress ``` From 4379525718e05c5e998f642f34308493fde823a6 Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Wed, 21 Sep 2022 13:34:16 +0000 Subject: [PATCH 15/41] typos and style corrections --- blueprints/third-party-solutions/wordpress/cloudrun/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/README.md b/blueprints/third-party-solutions/wordpress/cloudrun/README.md index 6aba6774..cd54ba39 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/README.md +++ b/blueprints/third-party-solutions/wordpress/cloudrun/README.md @@ -56,7 +56,7 @@ docker tag bitnami/wordpress gcr.io/MY_PROJECT/wordpress docker push gcr.io/MY_PROJECT/wordpress ``` -**Not**: This example has been built for this particular Docker image. If you decide to use another one, this example might not work (or you can edit the variables in the Terraform files). +**Note**: This example has been built for this particular Docker image. If you decide to use another one, this example might not work (or you can edit the variables in the Terraform files). #### Step 2: Deploy resources From 0722f7d124f876c8fb974d3474b0402bc65e7343 Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Wed, 21 Sep 2022 13:38:52 +0000 Subject: [PATCH 16/41] variables alphabetically --- .../wordpress/cloudrun/README.md | 22 +++---- .../wordpress/cloudrun/variables.tf | 60 +++++++++---------- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/README.md b/blueprints/third-party-solutions/wordpress/cloudrun/README.md index cd54ba39..030a36b7 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/README.md +++ b/blueprints/third-party-solutions/wordpress/cloudrun/README.md @@ -104,17 +104,17 @@ The above command will delete the associated resources so there will be no billa | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [project_id](variables.tf#L32) | Project id, references existing project if `project_create` is null. | string | ✓ | | -| [wordpress_image](variables.tf#L49) | Image to run with Cloud Run, starts with \"gcr.io\" | string | ✓ | | -| [cloud_run_invoker](variables.tf#L61) | IAM member authorized to access the end-point (for example, 'user:YOUR_IAM_USER' for only you or 'allUsers' for everyone) | string | | "allUsers" | -| [connector_cidr](variables.tf#L67) | CIDR block for the VPC serverless connector (10.8.0.0/28 by default) | string | | "10.8.0.0/28" | -| [prefix](variables.tf#L17) | Unique prefix used for resource names. Not used for project if 'project_create' is null. | string | | "" | -| [principals](variables.tf#L43) | List of emails of people/service accounts to give rights to, eg 'user@domain.com'. | list(string) | | [] | -| [project_create](variables.tf#L23) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | | null | -| [psa_cidr](variables.tf#L80) | CIDR block for Private Service Access for CloudSQL (10.60.0.0/24 by default) | string | | "10.60.0.0/24" | -| [region](variables.tf#L37) | Region for the created resources | string | | "europe-west4" | -| [sql_vpc_cidr](variables.tf#L73) | CIDR block for the VPC for the CloudSQL (10.0.0.0/20 by default) | string | | "10.0.0.0/20" | -| [wordpress_port](variables.tf#L54) | Port for the Wordpress image (8080 by default) | number | | 8080 | +| [project_id](variables.tf#L51) | Project id, references existing project if `project_create` is null. | string | ✓ | | +| [wordpress_image](variables.tf#L75) | Image to run with Cloud Run, starts with \"gcr.io\" | string | ✓ | | +| [cloud_run_invoker](variables.tf#L18) | IAM member authorized to access the end-point (for example, 'user:YOUR_IAM_USER' for only you or 'allUsers' for everyone) | string | | "allUsers" | +| [connector_cidr](variables.tf#L24) | CIDR block for the VPC serverless connector (10.8.0.0/28 by default) | string | | "10.8.0.0/28" | +| [prefix](variables.tf#L30) | Unique prefix used for resource names. Not used for project if 'project_create' is null. | string | | "" | +| [principals](variables.tf#L36) | List of emails of people/service accounts to give rights to, eg 'user@domain.com'. | list(string) | | [] | +| [project_create](variables.tf#L42) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | | null | +| [psa_cidr](variables.tf#L57) | CIDR block for Private Service Access for CloudSQL (10.60.0.0/24 by default) | string | | "10.60.0.0/24" | +| [region](variables.tf#L63) | Region for the created resources | string | | "europe-west4" | +| [sql_vpc_cidr](variables.tf#L69) | CIDR block for the VPC for the CloudSQL (10.0.0.0/20 by default) | string | | "10.0.0.0/20" | +| [wordpress_port](variables.tf#L80) | Port for the Wordpress image (8080 by default) | number | | 8080 | ## Outputs diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf b/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf index 0823b263..e9500db3 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf +++ b/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf @@ -14,12 +14,31 @@ * limitations under the License. */ +# Documentation: https://cloud.google.com/run/docs/securing/managing-access#making_a_service_public +variable "cloud_run_invoker" { + type = string + description = "IAM member authorized to access the end-point (for example, 'user:YOUR_IAM_USER' for only you or 'allUsers' for everyone)" + default = "allUsers" +} + +variable "connector_cidr" { + type = string + description = "CIDR block for the VPC serverless connector (10.8.0.0/28 by default)" + default = "10.8.0.0/28" +} + variable "prefix" { description = "Unique prefix used for resource names. Not used for project if 'project_create' is null." type = string default = "" } +variable "principals" { + description = "List of emails of people/service accounts to give rights to, eg 'user@domain.com'." + type = list(string) + default = [] +} + variable "project_create" { description = "Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format." type = object({ @@ -34,16 +53,23 @@ variable "project_id" { type = string } +# Documentation: https://cloud.google.com/vpc/docs/configure-private-services-access#allocating-range +variable "psa_cidr" { + type = string + description = "CIDR block for Private Service Access for CloudSQL (10.60.0.0/24 by default)" + default = "10.60.0.0/24" +} + variable "region" { type = string description = "Region for the created resources" default = "europe-west4" } -variable "principals" { - description = "List of emails of people/service accounts to give rights to, eg 'user@domain.com'." - type = list(string) - default = [] +variable "sql_vpc_cidr" { + type = string + description = "CIDR block for the VPC for the CloudSQL (10.0.0.0/20 by default)" + default = "10.0.0.0/20" } variable "wordpress_image" { @@ -55,30 +81,4 @@ variable "wordpress_port" { type = number description = "Port for the Wordpress image (8080 by default)" default = 8080 -} - -# Documentation: https://cloud.google.com/run/docs/securing/managing-access#making_a_service_public -variable "cloud_run_invoker" { - type = string - description = "IAM member authorized to access the end-point (for example, 'user:YOUR_IAM_USER' for only you or 'allUsers' for everyone)" - default = "allUsers" -} - -variable "connector_cidr" { - type = string - description = "CIDR block for the VPC serverless connector (10.8.0.0/28 by default)" - default = "10.8.0.0/28" -} - -variable "sql_vpc_cidr" { - type = string - description = "CIDR block for the VPC for the CloudSQL (10.0.0.0/20 by default)" - default = "10.0.0.0/20" -} - -# Documentation: https://cloud.google.com/vpc/docs/configure-private-services-access#allocating-range -variable "psa_cidr" { - type = string - description = "CIDR block for Private Service Access for CloudSQL (10.60.0.0/24 by default)" - default = "10.60.0.0/24" } \ No newline at end of file From d95f5d948b7017b90559c48c1f90a9947b8211d4 Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Wed, 21 Sep 2022 13:43:28 +0000 Subject: [PATCH 17/41] more readability --- .../wordpress/cloudrun/README.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/README.md b/blueprints/third-party-solutions/wordpress/cloudrun/README.md index 030a36b7..1313795b 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/README.md +++ b/blueprints/third-party-solutions/wordpress/cloudrun/README.md @@ -33,13 +33,16 @@ If `project_create` is left to null, the identity performing the deployment need #### Step 0: Cloning the repository -Click on the image below, sign in if required and when the prompt appears, click on “confirm”. +If you want to deploy from your Cloud Shell, click on the image below, sign in if required and when the prompt appears, click on “confirm”. -[

Open Cloudshell

]() +[

Open Cloudshell

](https://ssh.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https%3A%2F%2Fgithub.com%2FGoogleCloudPlatform%2Fcloud-foundation-fabric&cloudshell_print=cloud-shell-readme.txt&cloudshell_working_dir=blueprints%2Fthird-party-solutions%2Fwordpress) -LINK NEEDED --> can only be added after PR +Otherwise, in your console of choice: +``` {shell} +git clone https://github.com/GoogleCloudPlatform/cloud-foundation-fabric +``` -Before we deploy the architecture, you will at least need the following information (for more precise configuration see the Variables section): +Before you deploy the architecture, you will need at least the following information (for more precise configuration see the Variables section): * The project ID. * A Google Cloud Registry path to a Wordpress container image. @@ -60,9 +63,9 @@ docker push gcr.io/MY_PROJECT/wordpress #### Step 2: Deploy resources -Once you have the required information, head back to the Cloud Shell editor. Make sure you’re in the directory of this tutorial (where this README is in). +Once you have the required information, head back to your cloned repository. Make sure you’re in the directory of this tutorial (where this README is in). -Configure the Terraform variables in your terraform.tfvars file. See [terraform.tfvars.sample](terraform.tfvars.sample) as starting point - just copy it to `terraform.tfvars` and edit the latter. See the variables documentation below. +Configure the Terraform variables in your `terraform.tfvars` file. See [terraform.tfvars.sample](terraform.tfvars.sample) as starting point - just copy it to `terraform.tfvars` and edit the latter. See the variables documentation below. **Note**: If you have the [domain restriction org. policy](https://cloud.google.com/resource-manager/docs/organization-policy/restricting-domains) on your organization, you have to edit the `cloud_run_invoker` variable and give it a value that will be accepted in accordance to your policy. @@ -74,7 +77,7 @@ terraform apply ``` The resource creation will take a few minutes. -**Note**: you might get the following error (similar): +**Note**: you might get the following error (or a similar one): ``` {shell} │ Error: resource is in failed state "Ready:False", message: Revision '...' is not ready and cannot serve traffic.│ ``` From dbdaeda376509e65270bed7f7bbbb92721c09e08 Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Wed, 21 Sep 2022 13:46:38 +0000 Subject: [PATCH 18/41] style in locals --- .../wordpress/cloudrun/main.tf | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/main.tf b/blueprints/third-party-solutions/wordpress/cloudrun/main.tf index 72d50a86..31d4bfcb 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/main.tf +++ b/blueprints/third-party-solutions/wordpress/cloudrun/main.tf @@ -16,11 +16,14 @@ locals { - prefix = var.prefix == null ? "" : "${var.prefix}-" - all_principals_iam = [ - for k in var.principals : - "user:${k}" - ] + all_principals_iam = [for k in var.principals : "user:${k}"] + cloud_sql_conf = { + database_version = "MYSQL_8_0" + tier = "db-g1-small" + db = "wp-mysql" + user = "admin" + pass = "password" + } iam = { # CloudSQL "roles/cloudsql.admin" = local.all_principals_iam @@ -31,13 +34,7 @@ locals { "roles/iam.serviceAccountUser" = local.all_principals_iam "roles/iam.serviceAccountTokenCreator" = local.all_principals_iam } - cloud_sql_conf = { - database_version = "MYSQL_8_0" - tier = "db-g1-small" - db = "wp-mysql" - user = "admin" - pass = "password" - } + prefix = var.prefix == null ? "" : "${var.prefix}-" wp_user = "user" } @@ -115,7 +112,7 @@ module "cloud_run" { cloudsql_instances = [module.cloudsql.connection_name] vpcaccess_connector = null # allow all traffic - vpcaccess_egress = "all-traffic" + vpcaccess_egress = "all-traffic" } ingress_settings = "all" From b5a5150bb8a73736ef5ba01cc412edebdffeb396 Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Wed, 5 Oct 2022 14:09:48 +0000 Subject: [PATCH 19/41] docker image tag added --- blueprints/third-party-solutions/wordpress/cloudrun/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/README.md b/blueprints/third-party-solutions/wordpress/cloudrun/README.md index 1313795b..48f7f006 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/README.md +++ b/blueprints/third-party-solutions/wordpress/cloudrun/README.md @@ -54,7 +54,7 @@ In order to deploy the Wordpress service to Cloud Run, you need to store the [Wo Make sure that the Google Container Registry API is enabled and run the following commands in your Cloud Shell environment with your `project_id` in place of the `MY_PROJECT` placeholder: ``` {shell} -docker pull bitnami/wordpress +docker pull bitnami/wordpress:6.0.2 docker tag bitnami/wordpress gcr.io/MY_PROJECT/wordpress docker push gcr.io/MY_PROJECT/wordpress ``` From b11ae477385d0528cee4dbedb2ba53ea8db16e4b Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Wed, 5 Oct 2022 14:11:10 +0000 Subject: [PATCH 20/41] passwords: either specified or random --- .../wordpress/cloudrun/main.tf | 26 ++++++++++--------- .../wordpress/cloudrun/outputs.tf | 8 +++++- .../wordpress/cloudrun/variables.tf | 12 +++++++++ 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/main.tf b/blueprints/third-party-solutions/wordpress/cloudrun/main.tf index 31d4bfcb..8bf0aa66 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/main.tf +++ b/blueprints/third-party-solutions/wordpress/cloudrun/main.tf @@ -17,12 +17,12 @@ locals { all_principals_iam = [for k in var.principals : "user:${k}"] - cloud_sql_conf = { + cloudsql_conf = { database_version = "MYSQL_8_0" tier = "db-g1-small" db = "wp-mysql" user = "admin" - pass = "password" + pass = var.cloudsql_password == null ? random_password.cloudsql_password.result : var.cloudsql_password } iam = { # CloudSQL @@ -36,9 +36,9 @@ locals { } prefix = var.prefix == null ? "" : "${var.prefix}-" wp_user = "user" + wp_pass = var.wordpress_password == null ? random_password.wp_password.result : var.wordpress_password } - # either create a project or set up the given one module "project" { source = "../../../../modules/project" @@ -60,11 +60,13 @@ module "project" { ] } - resource "random_password" "wp_password" { length = 8 } +resource "random_password" "cloudsql_password" { + length = 8 +} # create the Cloud Run service module "cloud_run" { @@ -88,11 +90,11 @@ module "cloud_run" { env = { "APACHE_HTTP_PORT_NUMBER" : var.wordpress_port "WORDPRESS_DATABASE_HOST" : module.cloudsql.ip - "WORDPRESS_DATABASE_NAME" : local.cloud_sql_conf.db - "WORDPRESS_DATABASE_USER" : local.cloud_sql_conf.user - "WORDPRESS_DATABASE_PASSWORD" : local.cloud_sql_conf.pass + "WORDPRESS_DATABASE_NAME" : local.cloudsql_conf.db + "WORDPRESS_DATABASE_USER" : local.cloudsql_conf.user + "WORDPRESS_DATABASE_PASSWORD" : local.cloudsql_conf.pass "WORDPRESS_USERNAME" : local.wp_user - "WORDPRESS_PASSWORD" : random_password.wp_password.result + "WORDPRESS_PASSWORD" : local.wp_pass } } resources = null @@ -165,10 +167,10 @@ module "cloudsql" { network = module.vpc.self_link name = "${local.prefix}mysql" region = var.region - database_version = local.cloud_sql_conf.database_version - tier = local.cloud_sql_conf.tier - databases = [local.cloud_sql_conf.db] + database_version = local.cloudsql_conf.database_version + tier = local.cloudsql_conf.tier + databases = [local.cloudsql_conf.db] users = { - "${local.cloud_sql_conf.user}" = "${local.cloud_sql_conf.pass}" + "${local.cloudsql_conf.user}" = "${local.cloudsql_conf.pass}" } } \ No newline at end of file diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/outputs.tf b/blueprints/third-party-solutions/wordpress/cloudrun/outputs.tf index ce993660..3bd300c9 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/outputs.tf +++ b/blueprints/third-party-solutions/wordpress/cloudrun/outputs.tf @@ -20,6 +20,12 @@ output "cloud_run_service" { sensitive = true } +output "cloudsql_password" { + description = "CloudSQL password" + value = local.cloudsql_conf.pass + sensitive = true +} + output "wp_user" { description = "Wordpress username" value = local.wp_user @@ -27,6 +33,6 @@ output "wp_user" { output "wp_password" { description = "Wordpress user password" - value = random_password.wp_password.result + value = local.wp_pass sensitive = true } diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf b/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf index e9500db3..02d834a4 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf +++ b/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf @@ -21,6 +21,12 @@ variable "cloud_run_invoker" { default = "allUsers" } +variable "cloudsql_password" { + type = string + description = "CloudSQL password (will be randomly generated by default)" + default = null +} + variable "connector_cidr" { type = string description = "CIDR block for the VPC serverless connector (10.8.0.0/28 by default)" @@ -81,4 +87,10 @@ variable "wordpress_port" { type = number description = "Port for the Wordpress image (8080 by default)" default = 8080 +} + +variable "wordpress_password" { + type = string + description = "Password for the Wordpress user (will be randomly generated by default)" + default = null } \ No newline at end of file From 0411dbbd518df68ebfae54af93f3d8a0d86269ae Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Thu, 6 Oct 2022 07:58:48 +0000 Subject: [PATCH 21/41] ip_ranges var --- .../wordpress/cloudrun/README.md | 25 ++++++++-------- .../wordpress/cloudrun/main.tf | 8 ++--- .../wordpress/cloudrun/variables.tf | 30 ++++++++----------- 3 files changed, 30 insertions(+), 33 deletions(-) diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/README.md b/blueprints/third-party-solutions/wordpress/cloudrun/README.md index 48f7f006..0d5447db 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/README.md +++ b/blueprints/third-party-solutions/wordpress/cloudrun/README.md @@ -107,24 +107,25 @@ The above command will delete the associated resources so there will be no billa | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [project_id](variables.tf#L51) | Project id, references existing project if `project_create` is null. | string | ✓ | | -| [wordpress_image](variables.tf#L75) | Image to run with Cloud Run, starts with \"gcr.io\" | string | ✓ | | +| [project_id](variables.tf#L66) | Project id, references existing project if `project_create` is null. | string | ✓ | | +| [wordpress_image](variables.tf#L77) | Image to run with Cloud Run, starts with \"gcr.io\" | string | ✓ | | | [cloud_run_invoker](variables.tf#L18) | IAM member authorized to access the end-point (for example, 'user:YOUR_IAM_USER' for only you or 'allUsers' for everyone) | string | | "allUsers" | -| [connector_cidr](variables.tf#L24) | CIDR block for the VPC serverless connector (10.8.0.0/28 by default) | string | | "10.8.0.0/28" | -| [prefix](variables.tf#L30) | Unique prefix used for resource names. Not used for project if 'project_create' is null. | string | | "" | -| [principals](variables.tf#L36) | List of emails of people/service accounts to give rights to, eg 'user@domain.com'. | list(string) | | [] | -| [project_create](variables.tf#L42) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | | null | -| [psa_cidr](variables.tf#L57) | CIDR block for Private Service Access for CloudSQL (10.60.0.0/24 by default) | string | | "10.60.0.0/24" | -| [region](variables.tf#L63) | Region for the created resources | string | | "europe-west4" | -| [sql_vpc_cidr](variables.tf#L69) | CIDR block for the VPC for the CloudSQL (10.0.0.0/20 by default) | string | | "10.0.0.0/20" | -| [wordpress_port](variables.tf#L80) | Port for the Wordpress image (8080 by default) | number | | 8080 | +| [cloudsql_password](variables.tf#L24) | CloudSQL password (will be randomly generated by default) | string | | null | +| [ip_ranges](variables.tf#L31) | CIDR blocks: VPC serverless connector, Private Service Access(PSA) for CloudSQL, CloudSQL VPC | object({…}) | | {…} | +| [prefix](variables.tf#L45) | Unique prefix used for resource names. Not used for project if 'project_create' is null. | string | | "" | +| [principals](variables.tf#L51) | List of emails of people/service accounts to give rights to, eg 'user@domain.com'. | list(string) | | [] | +| [project_create](variables.tf#L57) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | | null | +| [region](variables.tf#L71) | Region for the created resources | string | | "europe-west4" | +| [wordpress_password](variables.tf#L88) | Password for the Wordpress user (will be randomly generated by default) | string | | null | +| [wordpress_port](variables.tf#L82) | Port for the Wordpress image (8080 by default) | number | | 8080 | ## Outputs | name | description | sensitive | |---|---|:---:| | [cloud_run_service](outputs.tf#L17) | CloudRun service URL | ✓ | -| [wp_password](outputs.tf#L28) | Wordpress user password | ✓ | -| [wp_user](outputs.tf#L23) | Wordpress username | | +| [cloudsql_password](outputs.tf#L23) | CloudSQL password | ✓ | +| [wp_password](outputs.tf#L34) | Wordpress user password | ✓ | +| [wp_user](outputs.tf#L29) | Wordpress username | | diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/main.tf b/blueprints/third-party-solutions/wordpress/cloudrun/main.tf index 8bf0aa66..afd23cf3 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/main.tf +++ b/blueprints/third-party-solutions/wordpress/cloudrun/main.tf @@ -120,7 +120,7 @@ module "cloud_run" { # create a VPC connector for the ClouSQL VPC vpc_connector_create = { - ip_cidr_range = var.connector_cidr + ip_cidr_range = var.ip_ranges.connector name = "${local.prefix}wp-connector" vpc_self_link = module.vpc.self_link } @@ -134,7 +134,7 @@ module "vpc" { name = "${local.prefix}sql-vpc" subnets = [ { - ip_cidr_range = var.sql_vpc_cidr + ip_cidr_range = var.ip_ranges.sql_vpc name = "subnet" region = var.region secondary_ip_range = {} @@ -144,7 +144,7 @@ module "vpc" { # Private Service Access psa_config = { ranges = { - cloud-sql = var.psa_cidr + cloud-sql = var.ip_ranges.psa } routes = null } @@ -156,7 +156,7 @@ module "firewall" { source = "../../../../modules/net-vpc-firewall" project_id = module.project.project_id network = module.vpc.name - admin_ranges = [var.sql_vpc_cidr] + admin_ranges = [var.ip_ranges.sql_vpc] } diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf b/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf index 02d834a4..adb14d7d 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf +++ b/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf @@ -27,10 +27,19 @@ variable "cloudsql_password" { default = null } -variable "connector_cidr" { - type = string - description = "CIDR block for the VPC serverless connector (10.8.0.0/28 by default)" - default = "10.8.0.0/28" +# PSA: documentation: https://cloud.google.com/vpc/docs/configure-private-services-access#allocating-range +variable "ip_ranges" { + description = "CIDR blocks: VPC serverless connector, Private Service Access(PSA) for CloudSQL, CloudSQL VPC" + type = object({ + connector = string + psa = string + sql_vpc = string + }) + default = { + connector = "10.8.0.0/28" + psa = "10.60.0.0/24" + sql_vpc = "10.0.0.0/20" + } } variable "prefix" { @@ -59,25 +68,12 @@ variable "project_id" { type = string } -# Documentation: https://cloud.google.com/vpc/docs/configure-private-services-access#allocating-range -variable "psa_cidr" { - type = string - description = "CIDR block for Private Service Access for CloudSQL (10.60.0.0/24 by default)" - default = "10.60.0.0/24" -} - variable "region" { type = string description = "Region for the created resources" default = "europe-west4" } -variable "sql_vpc_cidr" { - type = string - description = "CIDR block for the VPC for the CloudSQL (10.0.0.0/20 by default)" - default = "10.0.0.0/20" -} - variable "wordpress_image" { type = string description = "Image to run with Cloud Run, starts with \"gcr.io\"" From bea92728fb9be4c6bd42c17dc3b3afb8ccccde3b Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Thu, 6 Oct 2022 08:53:27 +0000 Subject: [PATCH 22/41] removed unnecessary README --- blueprints/third-party-solutions/wordpress/README.md | 1 - 1 file changed, 1 deletion(-) delete mode 100644 blueprints/third-party-solutions/wordpress/README.md diff --git a/blueprints/third-party-solutions/wordpress/README.md b/blueprints/third-party-solutions/wordpress/README.md deleted file mode 100644 index 46409041..00000000 --- a/blueprints/third-party-solutions/wordpress/README.md +++ /dev/null @@ -1 +0,0 @@ -# TODO From c72d3555a328694268322fd5a122cb6915b93618 Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Thu, 6 Oct 2022 09:10:40 +0000 Subject: [PATCH 23/41] principals varibale description: users --- blueprints/third-party-solutions/wordpress/cloudrun/README.md | 2 +- .../third-party-solutions/wordpress/cloudrun/variables.tf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/README.md b/blueprints/third-party-solutions/wordpress/cloudrun/README.md index 0d5447db..cc724508 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/README.md +++ b/blueprints/third-party-solutions/wordpress/cloudrun/README.md @@ -113,7 +113,7 @@ The above command will delete the associated resources so there will be no billa | [cloudsql_password](variables.tf#L24) | CloudSQL password (will be randomly generated by default) | string | | null | | [ip_ranges](variables.tf#L31) | CIDR blocks: VPC serverless connector, Private Service Access(PSA) for CloudSQL, CloudSQL VPC | object({…}) | | {…} | | [prefix](variables.tf#L45) | Unique prefix used for resource names. Not used for project if 'project_create' is null. | string | | "" | -| [principals](variables.tf#L51) | List of emails of people/service accounts to give rights to, eg 'user@domain.com'. | list(string) | | [] | +| [principals](variables.tf#L51) | List of emails of users to give rights to, eg 'user@domain.com'. | list(string) | | [] | | [project_create](variables.tf#L57) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | | null | | [region](variables.tf#L71) | Region for the created resources | string | | "europe-west4" | | [wordpress_password](variables.tf#L88) | Password for the Wordpress user (will be randomly generated by default) | string | | null | diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf b/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf index adb14d7d..df1891e0 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf +++ b/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf @@ -49,7 +49,7 @@ variable "prefix" { } variable "principals" { - description = "List of emails of people/service accounts to give rights to, eg 'user@domain.com'." + description = "List of emails of users to give rights to, eg 'user@domain.com'." type = list(string) default = [] } From c2056602e3c4ad2347c8dcfba4a3bb40b9334ed6 Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Thu, 6 Oct 2022 09:25:38 +0000 Subject: [PATCH 24/41] principals: rights list --- blueprints/third-party-solutions/wordpress/cloudrun/README.md | 2 +- .../third-party-solutions/wordpress/cloudrun/variables.tf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/README.md b/blueprints/third-party-solutions/wordpress/cloudrun/README.md index cc724508..e5b2cfd5 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/README.md +++ b/blueprints/third-party-solutions/wordpress/cloudrun/README.md @@ -113,7 +113,7 @@ The above command will delete the associated resources so there will be no billa | [cloudsql_password](variables.tf#L24) | CloudSQL password (will be randomly generated by default) | string | | null | | [ip_ranges](variables.tf#L31) | CIDR blocks: VPC serverless connector, Private Service Access(PSA) for CloudSQL, CloudSQL VPC | object({…}) | | {…} | | [prefix](variables.tf#L45) | Unique prefix used for resource names. Not used for project if 'project_create' is null. | string | | "" | -| [principals](variables.tf#L51) | List of emails of users to give rights to, eg 'user@domain.com'. | list(string) | | [] | +| [principals](variables.tf#L51) | List of users to give rights to (CloudSQL admin, client and instanceUser, Logging admin, Service Account User and TokenCreator), eg 'user@domain.com'. | list(string) | | [] | | [project_create](variables.tf#L57) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | | null | | [region](variables.tf#L71) | Region for the created resources | string | | "europe-west4" | | [wordpress_password](variables.tf#L88) | Password for the Wordpress user (will be randomly generated by default) | string | | null | diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf b/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf index df1891e0..c0ea7f4b 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf +++ b/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf @@ -49,7 +49,7 @@ variable "prefix" { } variable "principals" { - description = "List of emails of users to give rights to, eg 'user@domain.com'." + description = "List of users to give rights to (CloudSQL admin, client and instanceUser, Logging admin, Service Account User and TokenCreator), eg 'user@domain.com'." type = list(string) default = [] } From b6dcde6745494c050c17c41a8ef44cfe4fc11fda Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Thu, 6 Oct 2022 11:32:27 +0200 Subject: [PATCH 25/41] Info on the VPC serverless connector --- blueprints/third-party-solutions/wordpress/cloudrun/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/README.md b/blueprints/third-party-solutions/wordpress/cloudrun/README.md index e5b2cfd5..cf6d7a5c 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/README.md +++ b/blueprints/third-party-solutions/wordpress/cloudrun/README.md @@ -18,6 +18,7 @@ The main components that are deployed in this architecture are the following (yo * [Cloud Run](https://cloud.google.com/run): serverless PaaS offering to host containers for web-oriented applications, while offering security, scalability and easy versioning * [Cloud SQL](https://cloud.google.com/sql): Managed solution for SQL databases +* [VPC Serverless Connector](https://cloud.google.com/vpc/docs/serverless-vpc-access): Solution to access the CloudSQL VPC from Cloud Run, using only internal IP addresses ## Setup From aa3c6abcc2dcf2c92d87f17a5b7503e9f8b2da8d Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Thu, 6 Oct 2022 13:43:31 +0200 Subject: [PATCH 26/41] explanation on accessing the installation --- blueprints/third-party-solutions/wordpress/cloudrun/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/README.md b/blueprints/third-party-solutions/wordpress/cloudrun/README.md index cf6d7a5c..529df214 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/README.md +++ b/blueprints/third-party-solutions/wordpress/cloudrun/README.md @@ -92,6 +92,8 @@ terraform output # or for the concrete variable: terraform output cloud_run_service ``` +1. Open your browser at the URL that you get with that last command, and you will see your Wordpress installation. +2. Add "/admin" in the end of the URL and log in to the admin interface, using the outputs "wp_user" and "wp_password". ### Cleaning up your environment From c770abd88d575b9ad6f271f86a2910940da14a5c Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Thu, 6 Oct 2022 14:25:30 +0000 Subject: [PATCH 27/41] files restructured, connector added separately --- .../wordpress/cloudrun/cloudsql.tf | 78 +++++++++++++++++ .../wordpress/cloudrun/locals.tf | 40 +++++++++ .../wordpress/cloudrun/main.tf | 85 +------------------ 3 files changed, 119 insertions(+), 84 deletions(-) create mode 100644 blueprints/third-party-solutions/wordpress/cloudrun/cloudsql.tf create mode 100644 blueprints/third-party-solutions/wordpress/cloudrun/locals.tf diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/cloudsql.tf b/blueprints/third-party-solutions/wordpress/cloudrun/cloudsql.tf new file mode 100644 index 00000000..d726d761 --- /dev/null +++ b/blueprints/third-party-solutions/wordpress/cloudrun/cloudsql.tf @@ -0,0 +1,78 @@ +/** + * 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. + */ + + +resource "random_password" "cloudsql_password" { + length = 8 +} + +# create a VPC for CloudSQL +module "vpc" { + source = "../../../../modules/net-vpc" + project_id = module.project.project_id + name = "${local.prefix}sql-vpc" + subnets = [ + { + ip_cidr_range = var.ip_ranges.sql_vpc + name = "subnet" + region = var.region + secondary_ip_range = {} + } + ] + + # Private Service Access + psa_config = { + ranges = { + cloud-sql = var.ip_ranges.psa + } + routes = null + } +} + + +# set up firewall for CloudSQL +module "firewall" { + source = "../../../../modules/net-vpc-firewall" + project_id = module.project.project_id + network = module.vpc.name + admin_ranges = [var.ip_ranges.sql_vpc] +} + + +# create a VPC connector for the ClouSQL VPC +resource "google_vpc_access_connector" "connector" { + project = module.project.project_id + name = "${local.prefix}wp-connector" + region = var.region + ip_cidr_range = var.ip_ranges.connector + network = module.vpc.self_link +} + + +# Set up CloudSQL +module "cloudsql" { + source = "../../../../modules/cloudsql-instance" + project_id = module.project.project_id + network = module.vpc.self_link + name = "${local.prefix}mysql" + region = var.region + database_version = local.cloudsql_conf.database_version + tier = local.cloudsql_conf.tier + databases = [local.cloudsql_conf.db] + users = { + "${local.cloudsql_conf.user}" = "${local.cloudsql_conf.pass}" + } +} \ No newline at end of file diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/locals.tf b/blueprints/third-party-solutions/wordpress/cloudrun/locals.tf new file mode 100644 index 00000000..acf9220a --- /dev/null +++ b/blueprints/third-party-solutions/wordpress/cloudrun/locals.tf @@ -0,0 +1,40 @@ +/** + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +locals { + all_principals_iam = [for k in var.principals : "user:${k}"] + cloudsql_conf = { + database_version = "MYSQL_8_0" + tier = "db-g1-small" + db = "wp-mysql" + user = "admin" + pass = var.cloudsql_password == null ? random_password.cloudsql_password.result : var.cloudsql_password + } + iam = { + # CloudSQL + "roles/cloudsql.admin" = local.all_principals_iam + "roles/cloudsql.client" = local.all_principals_iam + "roles/cloudsql.instanceUser" = local.all_principals_iam + # common roles + "roles/logging.admin" = local.all_principals_iam + "roles/iam.serviceAccountUser" = local.all_principals_iam + "roles/iam.serviceAccountTokenCreator" = local.all_principals_iam + } + prefix = var.prefix == null ? "" : "${var.prefix}-" + wp_user = "user" + wp_pass = var.wordpress_password == null ? random_password.wp_password.result : var.wordpress_password +} \ No newline at end of file diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/main.tf b/blueprints/third-party-solutions/wordpress/cloudrun/main.tf index afd23cf3..41dda2e7 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/main.tf +++ b/blueprints/third-party-solutions/wordpress/cloudrun/main.tf @@ -15,30 +15,6 @@ */ -locals { - all_principals_iam = [for k in var.principals : "user:${k}"] - cloudsql_conf = { - database_version = "MYSQL_8_0" - tier = "db-g1-small" - db = "wp-mysql" - user = "admin" - pass = var.cloudsql_password == null ? random_password.cloudsql_password.result : var.cloudsql_password - } - iam = { - # CloudSQL - "roles/cloudsql.admin" = local.all_principals_iam - "roles/cloudsql.client" = local.all_principals_iam - "roles/cloudsql.instanceUser" = local.all_principals_iam - # common roles - "roles/logging.admin" = local.all_principals_iam - "roles/iam.serviceAccountUser" = local.all_principals_iam - "roles/iam.serviceAccountTokenCreator" = local.all_principals_iam - } - prefix = var.prefix == null ? "" : "${var.prefix}-" - wp_user = "user" - wp_pass = var.wordpress_password == null ? random_password.wp_password.result : var.wordpress_password -} - # either create a project or set up the given one module "project" { source = "../../../../modules/project" @@ -64,10 +40,6 @@ resource "random_password" "wp_password" { length = 8 } -resource "random_password" "cloudsql_password" { - length = 8 -} - # create the Cloud Run service module "cloud_run" { source = "../../../../modules/cloud-run" @@ -115,62 +87,7 @@ module "cloud_run" { vpcaccess_connector = null # allow all traffic vpcaccess_egress = "all-traffic" + vpcaccess_connector = google_vpc_access_connector.connector.self_link } ingress_settings = "all" - - # create a VPC connector for the ClouSQL VPC - vpc_connector_create = { - ip_cidr_range = var.ip_ranges.connector - name = "${local.prefix}wp-connector" - vpc_self_link = module.vpc.self_link - } -} - - -# create a VPC for CloudSQL -module "vpc" { - source = "../../../../modules/net-vpc" - project_id = module.project.project_id - name = "${local.prefix}sql-vpc" - subnets = [ - { - ip_cidr_range = var.ip_ranges.sql_vpc - name = "subnet" - region = var.region - secondary_ip_range = {} - } - ] - - # Private Service Access - psa_config = { - ranges = { - cloud-sql = var.ip_ranges.psa - } - routes = null - } -} - - -# set up firewall for CloudSQL -module "firewall" { - source = "../../../../modules/net-vpc-firewall" - project_id = module.project.project_id - network = module.vpc.name - admin_ranges = [var.ip_ranges.sql_vpc] -} - - -# Set up CloudSQL -module "cloudsql" { - source = "../../../../modules/cloudsql-instance" - project_id = module.project.project_id - network = module.vpc.self_link - name = "${local.prefix}mysql" - region = var.region - database_version = local.cloudsql_conf.database_version - tier = local.cloudsql_conf.tier - databases = [local.cloudsql_conf.db] - users = { - "${local.cloudsql_conf.user}" = "${local.cloudsql_conf.pass}" - } } \ No newline at end of file From e9779e30edf39a59873a05817f0642d628270c3b Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Fri, 7 Oct 2022 06:28:05 +0000 Subject: [PATCH 28/41] locals moved to main --- .../wordpress/cloudrun/locals.tf | 40 ------------------- .../wordpress/cloudrun/main.tf | 27 +++++++++++++ 2 files changed, 27 insertions(+), 40 deletions(-) delete mode 100644 blueprints/third-party-solutions/wordpress/cloudrun/locals.tf diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/locals.tf b/blueprints/third-party-solutions/wordpress/cloudrun/locals.tf deleted file mode 100644 index acf9220a..00000000 --- a/blueprints/third-party-solutions/wordpress/cloudrun/locals.tf +++ /dev/null @@ -1,40 +0,0 @@ -/** - * 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 { - all_principals_iam = [for k in var.principals : "user:${k}"] - cloudsql_conf = { - database_version = "MYSQL_8_0" - tier = "db-g1-small" - db = "wp-mysql" - user = "admin" - pass = var.cloudsql_password == null ? random_password.cloudsql_password.result : var.cloudsql_password - } - iam = { - # CloudSQL - "roles/cloudsql.admin" = local.all_principals_iam - "roles/cloudsql.client" = local.all_principals_iam - "roles/cloudsql.instanceUser" = local.all_principals_iam - # common roles - "roles/logging.admin" = local.all_principals_iam - "roles/iam.serviceAccountUser" = local.all_principals_iam - "roles/iam.serviceAccountTokenCreator" = local.all_principals_iam - } - prefix = var.prefix == null ? "" : "${var.prefix}-" - wp_user = "user" - wp_pass = var.wordpress_password == null ? random_password.wp_password.result : var.wordpress_password -} \ No newline at end of file diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/main.tf b/blueprints/third-party-solutions/wordpress/cloudrun/main.tf index 41dda2e7..296f1715 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/main.tf +++ b/blueprints/third-party-solutions/wordpress/cloudrun/main.tf @@ -15,6 +15,31 @@ */ +locals { + all_principals_iam = [for k in var.principals : "user:${k}"] + cloudsql_conf = { + database_version = "MYSQL_8_0" + tier = "db-g1-small" + db = "wp-mysql" + user = "admin" + pass = var.cloudsql_password == null ? random_password.cloudsql_password.result : var.cloudsql_password + } + iam = { + # CloudSQL + "roles/cloudsql.admin" = local.all_principals_iam + "roles/cloudsql.client" = local.all_principals_iam + "roles/cloudsql.instanceUser" = local.all_principals_iam + # common roles + "roles/logging.admin" = local.all_principals_iam + "roles/iam.serviceAccountUser" = local.all_principals_iam + "roles/iam.serviceAccountTokenCreator" = local.all_principals_iam + } + prefix = var.prefix == null ? "" : "${var.prefix}-" + wp_user = "user" + wp_pass = var.wordpress_password == null ? random_password.wp_password.result : var.wordpress_password +} + + # either create a project or set up the given one module "project" { source = "../../../../modules/project" @@ -36,10 +61,12 @@ module "project" { ] } + resource "random_password" "wp_password" { length = 8 } + # create the Cloud Run service module "cloud_run" { source = "../../../../modules/cloud-run" From 07f89e0aa0aeb41dbae1267f560a11f3e8f270e8 Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Fri, 7 Oct 2022 06:37:07 +0000 Subject: [PATCH 29/41] connector creation in a variable --- .../wordpress/cloudrun/README.md | 19 ++++++++++--------- .../wordpress/cloudrun/cloudsql.tf | 1 + .../wordpress/cloudrun/main.tf | 4 ++-- .../wordpress/cloudrun/variables.tf | 8 +++++++- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/README.md b/blueprints/third-party-solutions/wordpress/cloudrun/README.md index 529df214..4d43995b 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/README.md +++ b/blueprints/third-party-solutions/wordpress/cloudrun/README.md @@ -110,17 +110,18 @@ The above command will delete the associated resources so there will be no billa | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [project_id](variables.tf#L66) | Project id, references existing project if `project_create` is null. | string | ✓ | | -| [wordpress_image](variables.tf#L77) | Image to run with Cloud Run, starts with \"gcr.io\" | string | ✓ | | +| [project_id](variables.tf#L72) | Project id, references existing project if `project_create` is null. | string | ✓ | | +| [wordpress_image](variables.tf#L83) | Image to run with Cloud Run, starts with \"gcr.io\" | string | ✓ | | | [cloud_run_invoker](variables.tf#L18) | IAM member authorized to access the end-point (for example, 'user:YOUR_IAM_USER' for only you or 'allUsers' for everyone) | string | | "allUsers" | | [cloudsql_password](variables.tf#L24) | CloudSQL password (will be randomly generated by default) | string | | null | -| [ip_ranges](variables.tf#L31) | CIDR blocks: VPC serverless connector, Private Service Access(PSA) for CloudSQL, CloudSQL VPC | object({…}) | | {…} | -| [prefix](variables.tf#L45) | Unique prefix used for resource names. Not used for project if 'project_create' is null. | string | | "" | -| [principals](variables.tf#L51) | List of users to give rights to (CloudSQL admin, client and instanceUser, Logging admin, Service Account User and TokenCreator), eg 'user@domain.com'. | list(string) | | [] | -| [project_create](variables.tf#L57) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | | null | -| [region](variables.tf#L71) | Region for the created resources | string | | "europe-west4" | -| [wordpress_password](variables.tf#L88) | Password for the Wordpress user (will be randomly generated by default) | string | | null | -| [wordpress_port](variables.tf#L82) | Port for the Wordpress image (8080 by default) | number | | 8080 | +| [create_connector](variables.tf#L30) | Should a VPC serverless connector be created or not | bool | | true | +| [ip_ranges](variables.tf#L37) | CIDR blocks: VPC serverless connector, Private Service Access(PSA) for CloudSQL, CloudSQL VPC | object({…}) | | {…} | +| [prefix](variables.tf#L51) | Unique prefix used for resource names. Not used for project if 'project_create' is null. | string | | "" | +| [principals](variables.tf#L57) | List of users to give rights to (CloudSQL admin, client and instanceUser, Logging admin, Service Account User and TokenCreator), eg 'user@domain.com'. | list(string) | | [] | +| [project_create](variables.tf#L63) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | | null | +| [region](variables.tf#L77) | Region for the created resources | string | | "europe-west4" | +| [wordpress_password](variables.tf#L94) | Password for the Wordpress user (will be randomly generated by default) | string | | null | +| [wordpress_port](variables.tf#L88) | Port for the Wordpress image | number | | 8080 | ## Outputs diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/cloudsql.tf b/blueprints/third-party-solutions/wordpress/cloudrun/cloudsql.tf index d726d761..31a04315 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/cloudsql.tf +++ b/blueprints/third-party-solutions/wordpress/cloudrun/cloudsql.tf @@ -54,6 +54,7 @@ module "firewall" { # create a VPC connector for the ClouSQL VPC resource "google_vpc_access_connector" "connector" { + count = var.create_connector ? 1 : 0 project = module.project.project_id name = "${local.prefix}wp-connector" region = var.region diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/main.tf b/blueprints/third-party-solutions/wordpress/cloudrun/main.tf index 296f1715..f49b685b 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/main.tf +++ b/blueprints/third-party-solutions/wordpress/cloudrun/main.tf @@ -113,8 +113,8 @@ module "cloud_run" { cloudsql_instances = [module.cloudsql.connection_name] vpcaccess_connector = null # allow all traffic - vpcaccess_egress = "all-traffic" - vpcaccess_connector = google_vpc_access_connector.connector.self_link + vpcaccess_egress = "all-traffic" + vpcaccess_connector = google_vpc_access_connector.connector.0.self_link } ingress_settings = "all" } \ No newline at end of file diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf b/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf index c0ea7f4b..e56aaf8c 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf +++ b/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf @@ -27,6 +27,12 @@ variable "cloudsql_password" { default = null } +variable "create_connector" { + type = bool + description = "Should a VPC serverless connector be created or not" + default = true +} + # PSA: documentation: https://cloud.google.com/vpc/docs/configure-private-services-access#allocating-range variable "ip_ranges" { description = "CIDR blocks: VPC serverless connector, Private Service Access(PSA) for CloudSQL, CloudSQL VPC" @@ -81,7 +87,7 @@ variable "wordpress_image" { variable "wordpress_port" { type = number - description = "Port for the Wordpress image (8080 by default)" + description = "Port for the Wordpress image" default = 8080 } From 2f8a03a801b9d6fcc2fbf05c2ab76a612d6c1267 Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Fri, 7 Oct 2022 09:46:39 +0200 Subject: [PATCH 30/41] style and a note on password change --- .../wordpress/cloudrun/README.md | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/README.md b/blueprints/third-party-solutions/wordpress/cloudrun/README.md index 4d43995b..849eca53 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/README.md +++ b/blueprints/third-party-solutions/wordpress/cloudrun/README.md @@ -10,7 +10,7 @@ This architecture can be used for the following use cases and more: * Intranet / internal Wiki * E-commerce platform -## Architecture +# Architecture ![Wordpress on Cloud Run](images/architecture.png "Wordpress on Cloud Run") @@ -20,19 +20,19 @@ The main components that are deployed in this architecture are the following (yo * [Cloud SQL](https://cloud.google.com/sql): Managed solution for SQL databases * [VPC Serverless Connector](https://cloud.google.com/vpc/docs/serverless-vpc-access): Solution to access the CloudSQL VPC from Cloud Run, using only internal IP addresses -## Setup +# Setup -### Prerequisites +## Prerequisites -#### Setting up the project for the deployment +### Setting up the project for the deployment This example will deploy all its resources into the project defined by the `project_id` variable. Please note that we assume this project already exists. However, if you provide the appropriate values to the `project_create` variable, the project will be created as part of the deployment. If `project_create` is left to null, the identity performing the deployment needs the `owner` role on the project defined by the `project_id` variable. Otherwise, the identity performing the deployment needs `resourcemanager.projectCreator` on the resource hierarchy node specified by `project_create.parent` and `billing.user` on the billing account specified by `project_create.billing_account_id`. -### Deployment +## Deployment -#### Step 0: Cloning the repository +### Step 0: Cloning the repository If you want to deploy from your Cloud Shell, click on the image below, sign in if required and when the prompt appears, click on “confirm”. @@ -48,7 +48,7 @@ Before you deploy the architecture, you will need at least the following informa * The project ID. * A Google Cloud Registry path to a Wordpress container image. -#### Step 1: Add Wordpress image +### Step 1: Add Wordpress image In order to deploy the Wordpress service to Cloud Run, you need to store the [Wordpress image](https://hub.docker.com/r/bitnami/wordpress/) in Google Cloud Registry (GCR). @@ -62,13 +62,17 @@ docker push gcr.io/MY_PROJECT/wordpress **Note**: This example has been built for this particular Docker image. If you decide to use another one, this example might not work (or you can edit the variables in the Terraform files). -#### Step 2: Deploy resources +### Step 2: Prepare the variables Once you have the required information, head back to your cloned repository. Make sure you’re in the directory of this tutorial (where this README is in). Configure the Terraform variables in your `terraform.tfvars` file. See [terraform.tfvars.sample](terraform.tfvars.sample) as starting point - just copy it to `terraform.tfvars` and edit the latter. See the variables documentation below. -**Note**: If you have the [domain restriction org. policy](https://cloud.google.com/resource-manager/docs/organization-policy/restricting-domains) on your organization, you have to edit the `cloud_run_invoker` variable and give it a value that will be accepted in accordance to your policy. +**Notes**: +1. If you will want to change your admin password later on, please note that it will only work in the admin interface of Wordpress, but not with redeploying with Terraform, since Wordpress writes that password into the database upon installation and ignores the environment variables (that you can change with Terraform) after that. +2. If you have the [domain restriction org. policy](https://cloud.google.com/resource-manager/docs/organization-policy/restricting-domains) on your organization, you have to edit the `cloud_run_invoker` variable and give it a value that will be accepted in accordance to your policy. + +### Step 3: Deploy resources Initialize your Terraform environment and deploy the resources: @@ -84,7 +88,7 @@ The resource creation will take a few minutes. ``` You might try to reapply at this point, the Cloud Run service just needs several minutes. -#### Step 3: Use the created resources +### Step 4: Use the created resources Upon completion, you will see the output with the values for the Cloud Run service and the user and password to access the `/admin` part of the website. You can also view it later with: ``` {shell} @@ -95,7 +99,7 @@ terraform output cloud_run_service 1. Open your browser at the URL that you get with that last command, and you will see your Wordpress installation. 2. Add "/admin" in the end of the URL and log in to the admin interface, using the outputs "wp_user" and "wp_password". -### Cleaning up your environment +## Cleaning up your environment The easiest way to remove all the deployed resources is to run the following command in Cloud Shell: From a0171b2c49f6fbe3daf95c9a4e5bf2c683fe883e Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Fri, 7 Oct 2022 12:51:56 +0200 Subject: [PATCH 31/41] Bump terraform required version (#864) * bump terraform required version * fix test * debug test * debug test * disable test * fix CI file, bump terraform action version --- .github/workflows/linting.yml | 2 +- .github/workflows/tests.yml | 12 +++++-- blueprints/cloud-operations/adfs/versions.tf | 2 +- .../versions.tf | 2 +- .../dns-fine-grained-iam/versions.tf | 2 +- .../dns-shared-vpc/versions.tf | 2 +- .../iam-delegated-role-grants/versions.tf | 2 +- .../onprem-sa-key-management/versions.tf | 2 +- .../packer-image-builder/versions.tf | 2 +- .../quota-monitoring/versions.tf | 2 +- .../versions.tf | 2 +- .../cmek-via-centralized-kms/versions.tf | 2 +- .../data-playground/versions.tf | 2 +- .../versions.tf | 2 +- .../net-vpc-firewall-yaml/versions.tf | 2 +- .../decentralized-firewall/versions.tf | 2 +- .../networking/filtering-proxy/versions.tf | 2 +- .../hub-and-spoke-peering/versions.tf | 2 +- .../networking/hub-and-spoke-vpn/versions.tf | 2 +- .../networking/ilb-next-hop/versions.tf | 2 +- .../nginx-reverse-proxy-cluster/versions.tf | 2 +- .../onprem-google-access-dns/versions.tf | 2 +- .../versions.tf | 2 +- .../networking/shared-vpc-gke/versions.tf | 2 +- .../openshift/tf/versions.tf | 2 +- default-versions.tf | 2 +- fast/stages/00-cicd/versions.tf | 4 +-- modules/__experimental/net-neg/versions.tf | 2 +- modules/api-gateway/versions.tf | 2 +- modules/apigee-organization/versions.tf | 2 +- modules/apigee-x-instance/versions.tf | 2 +- modules/artifact-registry/versions.tf | 2 +- modules/bigquery-dataset/versions.tf | 2 +- modules/bigtable-instance/versions.tf | 2 +- modules/billing-budget/versions.tf | 2 +- modules/binauthz/versions.tf | 2 +- .../coredns/versions.tf | 2 +- .../cos-generic-metadata/versions.tf | 2 +- .../envoy-traffic-director/versions.tf | 2 +- .../cloud-config-container/mysql/versions.tf | 2 +- .../nginx-tls/versions.tf | 2 +- .../cloud-config-container/nginx/versions.tf | 2 +- .../cloud-config-container/onprem/versions.tf | 2 +- .../simple-nva/versions.tf | 2 +- .../cloud-config-container/squid/versions.tf | 2 +- modules/cloud-function/versions.tf | 2 +- modules/cloud-identity-group/versions.tf | 2 +- modules/cloud-run/versions.tf | 2 +- modules/cloudsql-instance/versions.tf | 2 +- modules/compute-mig/versions.tf | 2 +- modules/compute-vm/versions.tf | 2 +- modules/container-registry/versions.tf | 2 +- modules/data-catalog-policy-tag/versions.tf | 2 +- modules/datafusion/versions.tf | 2 +- modules/dns/versions.tf | 2 +- modules/endpoints/versions.tf | 2 +- modules/folder/versions.tf | 2 +- modules/gcs/versions.tf | 2 +- modules/gke-cluster/versions.tf | 2 +- modules/gke-hub/versions.tf | 2 +- modules/gke-nodepool/versions.tf | 2 +- modules/iam-service-account/versions.tf | 2 +- modules/kms/versions.tf | 2 +- modules/logging-bucket/versions.tf | 2 +- modules/net-address/versions.tf | 2 +- modules/net-cloudnat/versions.tf | 2 +- modules/net-glb/versions.tf | 2 +- modules/net-ilb-l7/versions.tf | 2 +- modules/net-ilb/versions.tf | 2 +- .../versions.tf | 2 +- modules/net-vpc-firewall/versions.tf | 2 +- modules/net-vpc-peering/versions.tf | 2 +- modules/net-vpc/versions.tf | 2 +- modules/net-vpn-dynamic/versions.tf | 2 +- modules/net-vpn-ha/versions.tf | 2 +- modules/net-vpn-static/versions.tf | 2 +- modules/organization-policy/versions.tf | 2 +- modules/organization/versions.tf | 2 +- modules/project/versions.tf | 2 +- modules/projects-data-source/versions.tf | 2 +- modules/pubsub/versions.tf | 2 +- modules/secret-manager/versions.tf | 2 +- modules/service-directory/versions.tf | 2 +- modules/source-repository/versions.tf | 2 +- modules/vpc-sc/versions.tf | 2 +- tests/fast/stages/s00_cicd/__init__.py | 13 ++++++++ tests/fast/stages/s00_cicd/test_providers.py | 33 +++++++++++++++++++ 87 files changed, 139 insertions(+), 89 deletions(-) create mode 100644 tests/fast/stages/s00_cicd/__init__.py create mode 100644 tests/fast/stages/s00_cicd/test_providers.py diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 33693873..21c0746a 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -37,7 +37,7 @@ jobs: - name: Set up Terraform uses: hashicorp/setup-terraform@v1 with: - terraform_version: 1.3 + terraform_version: 1.3.2 - name: Install dependencies run: | diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9dc0121a..bb46184f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -48,6 +48,12 @@ jobs: with: python-version: ${{ env.PYTHON_VERSION }} + - name: Set up Terraform + uses: hashicorp/setup-terraform@v2 + with: + terraform_version: ${{ env.TF_VERSION }} + terraform_wrapper: false + - name: Pin provider versions run: | sed -i 's/>=\(.*# tftest\)/=\1/g' default-versions.tf @@ -76,7 +82,7 @@ jobs: python-version: ${{ env.PYTHON_VERSION }} - name: Set up Terraform - uses: hashicorp/setup-terraform@v1 + uses: hashicorp/setup-terraform@v2 with: terraform_version: ${{ env.TF_VERSION }} terraform_wrapper: false @@ -109,7 +115,7 @@ jobs: python-version: ${{ env.PYTHON_VERSION }} - name: Set up Terraform - uses: hashicorp/setup-terraform@v1 + uses: hashicorp/setup-terraform@v2 with: terraform_version: ${{ env.TF_VERSION }} terraform_wrapper: false @@ -142,7 +148,7 @@ jobs: python-version: ${{ env.PYTHON_VERSION }} - name: Set up Terraform - uses: hashicorp/setup-terraform@v1 + uses: hashicorp/setup-terraform@v2 with: terraform_version: ${{ env.TF_VERSION }} terraform_wrapper: false diff --git a/blueprints/cloud-operations/adfs/versions.tf b/blueprints/cloud-operations/adfs/versions.tf index 8abac788..adb52a93 100644 --- a/blueprints/cloud-operations/adfs/versions.tf +++ b/blueprints/cloud-operations/adfs/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/blueprints/cloud-operations/asset-inventory-feed-remediation/versions.tf b/blueprints/cloud-operations/asset-inventory-feed-remediation/versions.tf index 8abac788..adb52a93 100644 --- a/blueprints/cloud-operations/asset-inventory-feed-remediation/versions.tf +++ b/blueprints/cloud-operations/asset-inventory-feed-remediation/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/blueprints/cloud-operations/dns-fine-grained-iam/versions.tf b/blueprints/cloud-operations/dns-fine-grained-iam/versions.tf index 8abac788..adb52a93 100644 --- a/blueprints/cloud-operations/dns-fine-grained-iam/versions.tf +++ b/blueprints/cloud-operations/dns-fine-grained-iam/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/blueprints/cloud-operations/dns-shared-vpc/versions.tf b/blueprints/cloud-operations/dns-shared-vpc/versions.tf index 8abac788..adb52a93 100644 --- a/blueprints/cloud-operations/dns-shared-vpc/versions.tf +++ b/blueprints/cloud-operations/dns-shared-vpc/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/blueprints/cloud-operations/iam-delegated-role-grants/versions.tf b/blueprints/cloud-operations/iam-delegated-role-grants/versions.tf index 8abac788..adb52a93 100644 --- a/blueprints/cloud-operations/iam-delegated-role-grants/versions.tf +++ b/blueprints/cloud-operations/iam-delegated-role-grants/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/blueprints/cloud-operations/onprem-sa-key-management/versions.tf b/blueprints/cloud-operations/onprem-sa-key-management/versions.tf index 8abac788..adb52a93 100644 --- a/blueprints/cloud-operations/onprem-sa-key-management/versions.tf +++ b/blueprints/cloud-operations/onprem-sa-key-management/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/blueprints/cloud-operations/packer-image-builder/versions.tf b/blueprints/cloud-operations/packer-image-builder/versions.tf index 8abac788..adb52a93 100644 --- a/blueprints/cloud-operations/packer-image-builder/versions.tf +++ b/blueprints/cloud-operations/packer-image-builder/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/blueprints/cloud-operations/quota-monitoring/versions.tf b/blueprints/cloud-operations/quota-monitoring/versions.tf index 8abac788..adb52a93 100644 --- a/blueprints/cloud-operations/quota-monitoring/versions.tf +++ b/blueprints/cloud-operations/quota-monitoring/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/blueprints/cloud-operations/scheduled-asset-inventory-export-bq/versions.tf b/blueprints/cloud-operations/scheduled-asset-inventory-export-bq/versions.tf index 8abac788..adb52a93 100644 --- a/blueprints/cloud-operations/scheduled-asset-inventory-export-bq/versions.tf +++ b/blueprints/cloud-operations/scheduled-asset-inventory-export-bq/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/blueprints/data-solutions/cmek-via-centralized-kms/versions.tf b/blueprints/data-solutions/cmek-via-centralized-kms/versions.tf index 8abac788..adb52a93 100644 --- a/blueprints/data-solutions/cmek-via-centralized-kms/versions.tf +++ b/blueprints/data-solutions/cmek-via-centralized-kms/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/blueprints/data-solutions/data-playground/versions.tf b/blueprints/data-solutions/data-playground/versions.tf index 8abac788..adb52a93 100644 --- a/blueprints/data-solutions/data-playground/versions.tf +++ b/blueprints/data-solutions/data-playground/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/blueprints/data-solutions/gcs-to-bq-with-least-privileges/versions.tf b/blueprints/data-solutions/gcs-to-bq-with-least-privileges/versions.tf index 8abac788..adb52a93 100644 --- a/blueprints/data-solutions/gcs-to-bq-with-least-privileges/versions.tf +++ b/blueprints/data-solutions/gcs-to-bq-with-least-privileges/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/blueprints/factories/net-vpc-firewall-yaml/versions.tf b/blueprints/factories/net-vpc-firewall-yaml/versions.tf index 8abac788..adb52a93 100644 --- a/blueprints/factories/net-vpc-firewall-yaml/versions.tf +++ b/blueprints/factories/net-vpc-firewall-yaml/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/blueprints/networking/decentralized-firewall/versions.tf b/blueprints/networking/decentralized-firewall/versions.tf index 8abac788..adb52a93 100644 --- a/blueprints/networking/decentralized-firewall/versions.tf +++ b/blueprints/networking/decentralized-firewall/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/blueprints/networking/filtering-proxy/versions.tf b/blueprints/networking/filtering-proxy/versions.tf index 8abac788..adb52a93 100644 --- a/blueprints/networking/filtering-proxy/versions.tf +++ b/blueprints/networking/filtering-proxy/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/blueprints/networking/hub-and-spoke-peering/versions.tf b/blueprints/networking/hub-and-spoke-peering/versions.tf index 8abac788..adb52a93 100644 --- a/blueprints/networking/hub-and-spoke-peering/versions.tf +++ b/blueprints/networking/hub-and-spoke-peering/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/blueprints/networking/hub-and-spoke-vpn/versions.tf b/blueprints/networking/hub-and-spoke-vpn/versions.tf index 8abac788..adb52a93 100644 --- a/blueprints/networking/hub-and-spoke-vpn/versions.tf +++ b/blueprints/networking/hub-and-spoke-vpn/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/blueprints/networking/ilb-next-hop/versions.tf b/blueprints/networking/ilb-next-hop/versions.tf index 8abac788..adb52a93 100644 --- a/blueprints/networking/ilb-next-hop/versions.tf +++ b/blueprints/networking/ilb-next-hop/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/blueprints/networking/nginx-reverse-proxy-cluster/versions.tf b/blueprints/networking/nginx-reverse-proxy-cluster/versions.tf index 8abac788..adb52a93 100644 --- a/blueprints/networking/nginx-reverse-proxy-cluster/versions.tf +++ b/blueprints/networking/nginx-reverse-proxy-cluster/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/blueprints/networking/onprem-google-access-dns/versions.tf b/blueprints/networking/onprem-google-access-dns/versions.tf index 8abac788..adb52a93 100644 --- a/blueprints/networking/onprem-google-access-dns/versions.tf +++ b/blueprints/networking/onprem-google-access-dns/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/blueprints/networking/private-cloud-function-from-onprem/versions.tf b/blueprints/networking/private-cloud-function-from-onprem/versions.tf index 8abac788..adb52a93 100644 --- a/blueprints/networking/private-cloud-function-from-onprem/versions.tf +++ b/blueprints/networking/private-cloud-function-from-onprem/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/blueprints/networking/shared-vpc-gke/versions.tf b/blueprints/networking/shared-vpc-gke/versions.tf index 8abac788..adb52a93 100644 --- a/blueprints/networking/shared-vpc-gke/versions.tf +++ b/blueprints/networking/shared-vpc-gke/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/blueprints/third-party-solutions/openshift/tf/versions.tf b/blueprints/third-party-solutions/openshift/tf/versions.tf index 8abac788..adb52a93 100644 --- a/blueprints/third-party-solutions/openshift/tf/versions.tf +++ b/blueprints/third-party-solutions/openshift/tf/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/default-versions.tf b/default-versions.tf index 8abac788..adb52a93 100644 --- a/default-versions.tf +++ b/default-versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/fast/stages/00-cicd/versions.tf b/fast/stages/00-cicd/versions.tf index 3a6a1ed8..e51caaa1 100644 --- a/fast/stages/00-cicd/versions.tf +++ b/fast/stages/00-cicd/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" @@ -33,5 +33,3 @@ terraform { version = ">= 3.16.1" } } - - diff --git a/modules/__experimental/net-neg/versions.tf b/modules/__experimental/net-neg/versions.tf index 8abac788..adb52a93 100644 --- a/modules/__experimental/net-neg/versions.tf +++ b/modules/__experimental/net-neg/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/api-gateway/versions.tf b/modules/api-gateway/versions.tf index 8abac788..adb52a93 100644 --- a/modules/api-gateway/versions.tf +++ b/modules/api-gateway/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/apigee-organization/versions.tf b/modules/apigee-organization/versions.tf index 8abac788..adb52a93 100644 --- a/modules/apigee-organization/versions.tf +++ b/modules/apigee-organization/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/apigee-x-instance/versions.tf b/modules/apigee-x-instance/versions.tf index 8abac788..adb52a93 100644 --- a/modules/apigee-x-instance/versions.tf +++ b/modules/apigee-x-instance/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/artifact-registry/versions.tf b/modules/artifact-registry/versions.tf index 8abac788..adb52a93 100644 --- a/modules/artifact-registry/versions.tf +++ b/modules/artifact-registry/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/bigquery-dataset/versions.tf b/modules/bigquery-dataset/versions.tf index 8abac788..adb52a93 100644 --- a/modules/bigquery-dataset/versions.tf +++ b/modules/bigquery-dataset/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/bigtable-instance/versions.tf b/modules/bigtable-instance/versions.tf index 8abac788..adb52a93 100644 --- a/modules/bigtable-instance/versions.tf +++ b/modules/bigtable-instance/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/billing-budget/versions.tf b/modules/billing-budget/versions.tf index 8abac788..adb52a93 100644 --- a/modules/billing-budget/versions.tf +++ b/modules/billing-budget/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/binauthz/versions.tf b/modules/binauthz/versions.tf index 8abac788..adb52a93 100644 --- a/modules/binauthz/versions.tf +++ b/modules/binauthz/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/cloud-config-container/coredns/versions.tf b/modules/cloud-config-container/coredns/versions.tf index 8abac788..adb52a93 100644 --- a/modules/cloud-config-container/coredns/versions.tf +++ b/modules/cloud-config-container/coredns/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/cloud-config-container/cos-generic-metadata/versions.tf b/modules/cloud-config-container/cos-generic-metadata/versions.tf index 8abac788..adb52a93 100644 --- a/modules/cloud-config-container/cos-generic-metadata/versions.tf +++ b/modules/cloud-config-container/cos-generic-metadata/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/cloud-config-container/envoy-traffic-director/versions.tf b/modules/cloud-config-container/envoy-traffic-director/versions.tf index 8abac788..adb52a93 100644 --- a/modules/cloud-config-container/envoy-traffic-director/versions.tf +++ b/modules/cloud-config-container/envoy-traffic-director/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/cloud-config-container/mysql/versions.tf b/modules/cloud-config-container/mysql/versions.tf index 8abac788..adb52a93 100644 --- a/modules/cloud-config-container/mysql/versions.tf +++ b/modules/cloud-config-container/mysql/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/cloud-config-container/nginx-tls/versions.tf b/modules/cloud-config-container/nginx-tls/versions.tf index 8abac788..adb52a93 100644 --- a/modules/cloud-config-container/nginx-tls/versions.tf +++ b/modules/cloud-config-container/nginx-tls/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/cloud-config-container/nginx/versions.tf b/modules/cloud-config-container/nginx/versions.tf index 8abac788..adb52a93 100644 --- a/modules/cloud-config-container/nginx/versions.tf +++ b/modules/cloud-config-container/nginx/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/cloud-config-container/onprem/versions.tf b/modules/cloud-config-container/onprem/versions.tf index 8abac788..adb52a93 100644 --- a/modules/cloud-config-container/onprem/versions.tf +++ b/modules/cloud-config-container/onprem/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/cloud-config-container/simple-nva/versions.tf b/modules/cloud-config-container/simple-nva/versions.tf index 8abac788..adb52a93 100644 --- a/modules/cloud-config-container/simple-nva/versions.tf +++ b/modules/cloud-config-container/simple-nva/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/cloud-config-container/squid/versions.tf b/modules/cloud-config-container/squid/versions.tf index 8abac788..adb52a93 100644 --- a/modules/cloud-config-container/squid/versions.tf +++ b/modules/cloud-config-container/squid/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/cloud-function/versions.tf b/modules/cloud-function/versions.tf index 8abac788..adb52a93 100644 --- a/modules/cloud-function/versions.tf +++ b/modules/cloud-function/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/cloud-identity-group/versions.tf b/modules/cloud-identity-group/versions.tf index 8abac788..adb52a93 100644 --- a/modules/cloud-identity-group/versions.tf +++ b/modules/cloud-identity-group/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/cloud-run/versions.tf b/modules/cloud-run/versions.tf index 8abac788..adb52a93 100644 --- a/modules/cloud-run/versions.tf +++ b/modules/cloud-run/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/cloudsql-instance/versions.tf b/modules/cloudsql-instance/versions.tf index 8abac788..adb52a93 100644 --- a/modules/cloudsql-instance/versions.tf +++ b/modules/cloudsql-instance/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/compute-mig/versions.tf b/modules/compute-mig/versions.tf index 8abac788..adb52a93 100644 --- a/modules/compute-mig/versions.tf +++ b/modules/compute-mig/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/compute-vm/versions.tf b/modules/compute-vm/versions.tf index 8abac788..adb52a93 100644 --- a/modules/compute-vm/versions.tf +++ b/modules/compute-vm/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/container-registry/versions.tf b/modules/container-registry/versions.tf index 8abac788..adb52a93 100644 --- a/modules/container-registry/versions.tf +++ b/modules/container-registry/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/data-catalog-policy-tag/versions.tf b/modules/data-catalog-policy-tag/versions.tf index 8abac788..adb52a93 100644 --- a/modules/data-catalog-policy-tag/versions.tf +++ b/modules/data-catalog-policy-tag/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/datafusion/versions.tf b/modules/datafusion/versions.tf index 8abac788..adb52a93 100644 --- a/modules/datafusion/versions.tf +++ b/modules/datafusion/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/dns/versions.tf b/modules/dns/versions.tf index 8abac788..adb52a93 100644 --- a/modules/dns/versions.tf +++ b/modules/dns/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/endpoints/versions.tf b/modules/endpoints/versions.tf index 8abac788..adb52a93 100644 --- a/modules/endpoints/versions.tf +++ b/modules/endpoints/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/folder/versions.tf b/modules/folder/versions.tf index 8abac788..adb52a93 100644 --- a/modules/folder/versions.tf +++ b/modules/folder/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/gcs/versions.tf b/modules/gcs/versions.tf index 8abac788..adb52a93 100644 --- a/modules/gcs/versions.tf +++ b/modules/gcs/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/gke-cluster/versions.tf b/modules/gke-cluster/versions.tf index 8abac788..adb52a93 100644 --- a/modules/gke-cluster/versions.tf +++ b/modules/gke-cluster/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/gke-hub/versions.tf b/modules/gke-hub/versions.tf index 8abac788..adb52a93 100644 --- a/modules/gke-hub/versions.tf +++ b/modules/gke-hub/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/gke-nodepool/versions.tf b/modules/gke-nodepool/versions.tf index 8abac788..adb52a93 100644 --- a/modules/gke-nodepool/versions.tf +++ b/modules/gke-nodepool/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/iam-service-account/versions.tf b/modules/iam-service-account/versions.tf index 8abac788..adb52a93 100644 --- a/modules/iam-service-account/versions.tf +++ b/modules/iam-service-account/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/kms/versions.tf b/modules/kms/versions.tf index 8abac788..adb52a93 100644 --- a/modules/kms/versions.tf +++ b/modules/kms/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/logging-bucket/versions.tf b/modules/logging-bucket/versions.tf index 8abac788..adb52a93 100644 --- a/modules/logging-bucket/versions.tf +++ b/modules/logging-bucket/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/net-address/versions.tf b/modules/net-address/versions.tf index 8abac788..adb52a93 100644 --- a/modules/net-address/versions.tf +++ b/modules/net-address/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/net-cloudnat/versions.tf b/modules/net-cloudnat/versions.tf index 8abac788..adb52a93 100644 --- a/modules/net-cloudnat/versions.tf +++ b/modules/net-cloudnat/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/net-glb/versions.tf b/modules/net-glb/versions.tf index 8abac788..adb52a93 100644 --- a/modules/net-glb/versions.tf +++ b/modules/net-glb/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/net-ilb-l7/versions.tf b/modules/net-ilb-l7/versions.tf index 8abac788..adb52a93 100644 --- a/modules/net-ilb-l7/versions.tf +++ b/modules/net-ilb-l7/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/net-ilb/versions.tf b/modules/net-ilb/versions.tf index 8abac788..adb52a93 100644 --- a/modules/net-ilb/versions.tf +++ b/modules/net-ilb/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/net-interconnect-attachment-direct/versions.tf b/modules/net-interconnect-attachment-direct/versions.tf index 8abac788..adb52a93 100644 --- a/modules/net-interconnect-attachment-direct/versions.tf +++ b/modules/net-interconnect-attachment-direct/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/net-vpc-firewall/versions.tf b/modules/net-vpc-firewall/versions.tf index 8abac788..adb52a93 100644 --- a/modules/net-vpc-firewall/versions.tf +++ b/modules/net-vpc-firewall/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/net-vpc-peering/versions.tf b/modules/net-vpc-peering/versions.tf index 8abac788..adb52a93 100644 --- a/modules/net-vpc-peering/versions.tf +++ b/modules/net-vpc-peering/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/net-vpc/versions.tf b/modules/net-vpc/versions.tf index 8abac788..adb52a93 100644 --- a/modules/net-vpc/versions.tf +++ b/modules/net-vpc/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/net-vpn-dynamic/versions.tf b/modules/net-vpn-dynamic/versions.tf index 8abac788..adb52a93 100644 --- a/modules/net-vpn-dynamic/versions.tf +++ b/modules/net-vpn-dynamic/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/net-vpn-ha/versions.tf b/modules/net-vpn-ha/versions.tf index 8abac788..adb52a93 100644 --- a/modules/net-vpn-ha/versions.tf +++ b/modules/net-vpn-ha/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/net-vpn-static/versions.tf b/modules/net-vpn-static/versions.tf index 8abac788..adb52a93 100644 --- a/modules/net-vpn-static/versions.tf +++ b/modules/net-vpn-static/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/organization-policy/versions.tf b/modules/organization-policy/versions.tf index 8abac788..adb52a93 100644 --- a/modules/organization-policy/versions.tf +++ b/modules/organization-policy/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/organization/versions.tf b/modules/organization/versions.tf index 8abac788..adb52a93 100644 --- a/modules/organization/versions.tf +++ b/modules/organization/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/project/versions.tf b/modules/project/versions.tf index 8abac788..adb52a93 100644 --- a/modules/project/versions.tf +++ b/modules/project/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/projects-data-source/versions.tf b/modules/projects-data-source/versions.tf index 8abac788..adb52a93 100644 --- a/modules/projects-data-source/versions.tf +++ b/modules/projects-data-source/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/pubsub/versions.tf b/modules/pubsub/versions.tf index 8abac788..adb52a93 100644 --- a/modules/pubsub/versions.tf +++ b/modules/pubsub/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/secret-manager/versions.tf b/modules/secret-manager/versions.tf index 8abac788..adb52a93 100644 --- a/modules/secret-manager/versions.tf +++ b/modules/secret-manager/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/service-directory/versions.tf b/modules/service-directory/versions.tf index 8abac788..adb52a93 100644 --- a/modules/service-directory/versions.tf +++ b/modules/service-directory/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/source-repository/versions.tf b/modules/source-repository/versions.tf index 8abac788..adb52a93 100644 --- a/modules/source-repository/versions.tf +++ b/modules/source-repository/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/modules/vpc-sc/versions.tf b/modules/vpc-sc/versions.tf index 8abac788..adb52a93 100644 --- a/modules/vpc-sc/versions.tf +++ b/modules/vpc-sc/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3.2" required_providers { google = { source = "hashicorp/google" diff --git a/tests/fast/stages/s00_cicd/__init__.py b/tests/fast/stages/s00_cicd/__init__.py new file mode 100644 index 00000000..6d6d1266 --- /dev/null +++ b/tests/fast/stages/s00_cicd/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/tests/fast/stages/s00_cicd/test_providers.py b/tests/fast/stages/s00_cicd/test_providers.py new file mode 100644 index 00000000..f2b39e91 --- /dev/null +++ b/tests/fast/stages/s00_cicd/test_providers.py @@ -0,0 +1,33 @@ +# 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. + +import pathlib +''' +github = { + source = "integrations/github" + version = "~> 4.0" +} +gitlab = { + source = "gitlabhq/gitlab" + version = ">= 3.16.1" +} +''' + +# def test_providers(): +# "Test providers file." +# p = pathlib.Path(__file__).parents[4] +# with (p / 'fast/stages/00-cicd/versions.tf').open() as f: +# data = f.read() +# assert 'integrations/github' in data +# assert 'gitlabhq/gitlab' in data From 78d1a09aeb6d8c8eed7563a10759ede1bf7df27e Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Fri, 7 Oct 2022 13:20:56 +0200 Subject: [PATCH 32/41] Enable FAST 00-cicd provider test (#865) * enable fast 00-cicd provider test * don't overwrite version files in CI * change provider pinning for all tests in CI file --- .github/workflows/tests.yml | 24 +++++++++++++------- tests/conftest.py | 5 ++++ tests/fast/stages/s00_cicd/test_providers.py | 17 +++++++------- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bb46184f..2a903525 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -54,10 +54,12 @@ jobs: terraform_version: ${{ env.TF_VERSION }} terraform_wrapper: false + # avoid conflicts with user-installed providers on local machines - name: Pin provider versions run: | - sed -i 's/>=\(.*# tftest\)/=\1/g' default-versions.tf - find -name versions.tf -exec cp default-versions.tf {} \; + for f in $(find . -name versions.tf); do + sed -i 's/>=\(.*# tftest\)/=\1/g' $f; + done - name: Run tests on documentation examples id: pytest @@ -87,10 +89,12 @@ jobs: terraform_version: ${{ env.TF_VERSION }} terraform_wrapper: false + # avoid conflicts with user-installed providers on local machines - name: Pin provider versions run: | - sed -i 's/>=\(.*# tftest\)/=\1/g' default-versions.tf - find -name versions.tf -exec cp default-versions.tf {} \; + for f in $(find . -name versions.tf); do + sed -i 's/>=\(.*# tftest\)/=\1/g' $f; + done - name: Run tests environments id: pytest @@ -120,10 +124,12 @@ jobs: terraform_version: ${{ env.TF_VERSION }} terraform_wrapper: false + # avoid conflicts with user-installed providers on local machines - name: Pin provider versions run: | - sed -i 's/>=\(.*# tftest\)/=\1/g' default-versions.tf - find -name versions.tf -exec cp default-versions.tf {} \; + for f in $(find . -name versions.tf); do + sed -i 's/>=\(.*# tftest\)/=\1/g' $f; + done - name: Run tests modules id: pytest @@ -153,10 +159,12 @@ jobs: terraform_version: ${{ env.TF_VERSION }} terraform_wrapper: false + # avoid conflicts with user-installed providers on local machines - name: Pin provider versions run: | - sed -i 's/>=\(.*# tftest\)/=\1/g' default-versions.tf - find -name versions.tf -exec cp default-versions.tf {} \; + for f in $(find . -name versions.tf); do + sed -i 's/>=\(.*# tftest\)/=\1/g' $f; + done - name: Run tests on FAST stages id: pytest diff --git a/tests/conftest.py b/tests/conftest.py index 48498db5..a5ded070 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -145,3 +145,8 @@ def apply_runner(): return apply, output return run_apply + + +@pytest.fixture +def basedir(): + return BASEDIR diff --git a/tests/fast/stages/s00_cicd/test_providers.py b/tests/fast/stages/s00_cicd/test_providers.py index f2b39e91..e45c869e 100644 --- a/tests/fast/stages/s00_cicd/test_providers.py +++ b/tests/fast/stages/s00_cicd/test_providers.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import pathlib +import os ''' github = { source = "integrations/github" @@ -24,10 +24,11 @@ gitlab = { } ''' -# def test_providers(): -# "Test providers file." -# p = pathlib.Path(__file__).parents[4] -# with (p / 'fast/stages/00-cicd/versions.tf').open() as f: -# data = f.read() -# assert 'integrations/github' in data -# assert 'gitlabhq/gitlab' in data + +def test_providers(basedir): + "Test providers file." + p = os.path.join(basedir, 'fast/stages/00-cicd/versions.tf') + with open(p) as f: + data = f.read() + assert 'integrations/github' in data + assert 'gitlabhq/gitlab' in data From 14d1fd2b6bb9d98fa29991fe78331ed219dec01d Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Fri, 7 Oct 2022 14:47:51 +0200 Subject: [PATCH 33/41] Fabric vs CFT doc (#863) * first unedited conversion * Add some external references * add link from home, minimal reformatting Co-authored-by: Julio Castillo --- FABRIC-AND-CFT.md | 170 ++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 +- 2 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 FABRIC-AND-CFT.md diff --git a/FABRIC-AND-CFT.md b/FABRIC-AND-CFT.md new file mode 100644 index 00000000..7ddba3f2 --- /dev/null +++ b/FABRIC-AND-CFT.md @@ -0,0 +1,170 @@ +# Cloud Foundation Fabric and Cloud Foundation Toolkit + +This page highlights the main differences (both technical and philosophical) between Cloud Foundation Fabric and Cloud Foundation Toolkit for end users and guide them in their decision making process for identifying the best suite of modules for their use cases. + +## Cloud Foundation Fabric (a.k.a Fabric, this repo) + +Fabric is a collection of Terraform modules and end to end examples meant to be cloned as a single unit and used as is for fast prototyping or decomposed and modified for usage in organizations. + +## Cloud Foundation Toolkit (a.k.a CFT) + +CFT is a collection of Terraform modules and examples with opinionated GCP best practices implemented as individual modules for gradual adoption and off the shelf usage in organizations. + +## Third-party reviews + +* [Google Cloud Landing Zone Comparison](https://www.meshcloud.io/2022/09/09/gcp-landing-zone-comparison/) by Meshcloud. + +## Key Differences + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Fabric + CFT +
Target User + Organizations interested in forking, maintaining and customizing Terraform modules. + Organizations interested in using opinionated, prebuilt Terraform modules. +
Configuration + Less opinionated allowing end users higher flexibility. + Opinionated by default, end users may need to fork if it does not meet their use case. +
Extensibility + Built with extensibility in mind catering to fork and use patterns. Modules are often lightweight and easy to adopt / tailor to specific use cases. + Not built with fork and use extensibility, caters to off the shelf consumption. +
Config customization + Prefer customization using variables via objects, tight variable space. + Prefer customization using variables via primitives. +
Examples + Thorough examples for individual modules, and end to end examples composing multiple modules covering a wide variety of use cases from foundations to solutions. + Examples for a module mostly focus on that individual module. \ + \ +Composition is often not shown in examples but in other modules built using smaller modules. +
Resources + Leaner modules wrapping resources. + Heavier root modules that often compose leaner sub modules wrapping resources. +
Resource grouping + Generally grouped by logical entities. + Generally grouped by products/product areas. +
Release Cadence + Modules versioned and released together. + Modules versioned and released individually. +
Individual module usage + Individual modules consumed directly using Git as a module source. +

+For production usage, we encourage customers to “fork and own” their own repository. +

Individual repositories consumed via the Terraform registry. +

+For production/airgapped usage, customers may also mirror modules to a private registry. +

Factories + Fabric implements several "factories" in modules, where users can drive or automate Terraform via YAML files (projects, subnetworks, firewalls, etc.). + +
Organizational adoption + Mono repo cloned into an organizational VCS (or catalog) and separated into individual modules for internal consumption. + Individual repos forked (for air gap) or wrapping upstream sources to create individual modules for internal consumption. +
Distribution + Distributed via Git/GitHub. + Distributed via Git/GitHub and Terraform Registry. +
Testing + Every PR performs unit tests on modules, examples, and documentation snippets by evaluating a Terraform plan via Python tftest library. + Every PR performs full end-to-end deployment with integration tests using the blueprint test framework. +
+ +## Similarities + +* Both collections of modules are designed with stable interfaces that work well together with other modules in their ecosystem. +* Both collections of modules require minimal variables and provide defaults. +* Both collections of modules are well tested and documented with information about usage, code snippets and provide information about variables and outputs. + +## Should you choose Fabric or CFT? + +> You/Your organization is knowledgeable in Terraform and interested in forking and owning a collection of modules. + + Fabric is a better choice as it bootstraps you with a collection of modules out of the box that can be customized exactly to fit your organization needs. + +> You/Your organization is getting started with Terraform and interested in GCP best practices out of the box. + + CFT is a better choice as it allows you to directly reference specific modules from the registry and provide opinionated configuration by default. + +> You/Your organization is looking to rapidly prototype some functionality on GCP. + + Fabric is a better choice. Being a mono repo it allows you to get started quickly with all your source code in one place for easier debugging. + +> You/Your organization has existing infrastructure and processes but want to start adopting IaC gradually. + + CFT is designed to be modular and off the shelf, providing higher level abstractions to product groups which allows certain teams to adopt Terraform without maintenance burden while allowing others to follow existing practices. + +## Using Fabric and CFT together + +Even with all the above points, it may be hard to make a decision. While the modules may have different patterns and philosophies, it is often possible to bring the best of both worlds together. Here are some tips to follow: + +* Since modules work well together within their ecosystem, select logical boundaries for using Fabric or CFT. For example use CFT for deploying resources within projects but use Fabric for managing project creation and IAM. +* Use strengths of each collection of modules to your advantage. Empower application teams to define their infrastructure as code using off the shelf CFT modules. Using Fabric, bootstrap your platform team with a collection of tailor built modules for your organization. +* Lean into module composition and dependency inversion that both Fabric and CFT modules follow. For example, you can create a GKE cluster using either [Fabric](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/tree/master/modules/gke-cluster#gke-cluster-module) or [CFT](https://github.com/terraform-google-modules/terraform-google-kubernetes-engine) GKE module and then use either [Fabric](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/tree/master/modules/gke-hub#variables) or [CFT](https://github.com/terraform-google-modules/terraform-google-kubernetes-engine/tree/master/modules/fleet-membership) for setting up GKE Hub by passing in outputs from the GKE module. diff --git a/README.md b/README.md index bdb17b65..70d5d666 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ This repository provides **end-to-end blueprints** and a **suite of Terraform mo - reference [blueprints](./blueprints/) used to deep dive on network patterns or product features - a comprehensive source of lean [modules](./modules/dns) that lend themselves well to changes -The whole repository is meant to be cloned as a single unit, and then forked into separate owned repositories to seed production usage, or used as-is and periodically updated as a complete toolkit for prototyping. You can read more on this approach in our [contributing guide](./CONTRIBUTING.md). +The whole repository is meant to be cloned as a single unit, and then forked into separate owned repositories to seed production usage, or used as-is and periodically updated as a complete toolkit for prototyping. You can read more on this approach in our [contributing guide](./CONTRIBUTING.md), and a comparison against similar toolkits [here](./FABRIC-AND-CFT.md). ## Organization blueprint (Fabric FAST) From 8a8a3fd76a7d7c695b190ec3823ab2e8aa32aa57 Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Fri, 7 Oct 2022 13:13:15 +0000 Subject: [PATCH 34/41] firewall unnecessary --- .../third-party-solutions/wordpress/cloudrun/cloudsql.tf | 9 --------- 1 file changed, 9 deletions(-) diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/cloudsql.tf b/blueprints/third-party-solutions/wordpress/cloudrun/cloudsql.tf index 31a04315..11e6e311 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/cloudsql.tf +++ b/blueprints/third-party-solutions/wordpress/cloudrun/cloudsql.tf @@ -43,15 +43,6 @@ module "vpc" { } -# set up firewall for CloudSQL -module "firewall" { - source = "../../../../modules/net-vpc-firewall" - project_id = module.project.project_id - network = module.vpc.name - admin_ranges = [var.ip_ranges.sql_vpc] -} - - # create a VPC connector for the ClouSQL VPC resource "google_vpc_access_connector" "connector" { count = var.create_connector ? 1 : 0 From 8eae3525b3e8b56b7bfbd608e635d7596bcc4093 Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Fri, 7 Oct 2022 13:29:55 +0000 Subject: [PATCH 35/41] existing connector - variable --- blueprints/third-party-solutions/wordpress/cloudrun/main.tf | 3 ++- .../third-party-solutions/wordpress/cloudrun/variables.tf | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/main.tf b/blueprints/third-party-solutions/wordpress/cloudrun/main.tf index f49b685b..abff617b 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/main.tf +++ b/blueprints/third-party-solutions/wordpress/cloudrun/main.tf @@ -34,6 +34,7 @@ locals { "roles/iam.serviceAccountUser" = local.all_principals_iam "roles/iam.serviceAccountTokenCreator" = local.all_principals_iam } + connector = var.connector == null ? google_vpc_access_connector.connector.0.self_link : var.connector prefix = var.prefix == null ? "" : "${var.prefix}-" wp_user = "user" wp_pass = var.wordpress_password == null ? random_password.wp_password.result : var.wordpress_password @@ -114,7 +115,7 @@ module "cloud_run" { vpcaccess_connector = null # allow all traffic vpcaccess_egress = "all-traffic" - vpcaccess_connector = google_vpc_access_connector.connector.0.self_link + vpcaccess_connector = local.connector } ingress_settings = "all" } \ No newline at end of file diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf b/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf index e56aaf8c..eaa2543b 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf +++ b/blueprints/third-party-solutions/wordpress/cloudrun/variables.tf @@ -27,6 +27,12 @@ variable "cloudsql_password" { default = null } +variable "connector" { + type = string + description = "Existing VPC serverless connector to use if not creating a new one" + default = null +} + variable "create_connector" { type = bool description = "Should a VPC serverless connector be created or not" From eb47e03d5df3eccf1a2de4dd9ed736ff3b594fd9 Mon Sep 17 00:00:00 2001 From: Natalia Strelkova Date: Fri, 7 Oct 2022 13:31:15 +0000 Subject: [PATCH 36/41] formatting --- blueprints/third-party-solutions/wordpress/cloudrun/main.tf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/blueprints/third-party-solutions/wordpress/cloudrun/main.tf b/blueprints/third-party-solutions/wordpress/cloudrun/main.tf index abff617b..3264619c 100644 --- a/blueprints/third-party-solutions/wordpress/cloudrun/main.tf +++ b/blueprints/third-party-solutions/wordpress/cloudrun/main.tf @@ -35,9 +35,9 @@ locals { "roles/iam.serviceAccountTokenCreator" = local.all_principals_iam } connector = var.connector == null ? google_vpc_access_connector.connector.0.self_link : var.connector - prefix = var.prefix == null ? "" : "${var.prefix}-" - wp_user = "user" - wp_pass = var.wordpress_password == null ? random_password.wp_password.result : var.wordpress_password + prefix = var.prefix == null ? "" : "${var.prefix}-" + wp_user = "user" + wp_pass = var.wordpress_password == null ? random_password.wp_password.result : var.wordpress_password } From 2de158e1410c4f3baf760a4aa099116e7264670d Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Fri, 7 Oct 2022 16:58:06 +0200 Subject: [PATCH 37/41] Update FABRIC-AND-CFT.md --- FABRIC-AND-CFT.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FABRIC-AND-CFT.md b/FABRIC-AND-CFT.md index 7ddba3f2..1b8de626 100644 --- a/FABRIC-AND-CFT.md +++ b/FABRIC-AND-CFT.md @@ -1,6 +1,6 @@ # Cloud Foundation Fabric and Cloud Foundation Toolkit -This page highlights the main differences (both technical and philosophical) between Cloud Foundation Fabric and Cloud Foundation Toolkit for end users and guide them in their decision making process for identifying the best suite of modules for their use cases. +This page highlights the main differences (both technical and philosophical) between Cloud Foundation Fabric and Cloud Foundation Toolkit for end users, to guide them in their decision making process for identifying the best suite of modules for their use cases. ## Cloud Foundation Fabric (a.k.a Fabric, this repo) @@ -151,7 +151,7 @@ For production/airgapped usage, customers may also mirror modules to a private r > You/Your organization is getting started with Terraform and interested in GCP best practices out of the box. - CFT is a better choice as it allows you to directly reference specific modules from the registry and provide opinionated configuration by default. + CFT is a better choice as it allows you to directly reference specific modules from the registry and provide opinionated configuration by default. > You/Your organization is looking to rapidly prototype some functionality on GCP. From ee30ac0c76a13bddc9151278c6d7bed2620db1c7 Mon Sep 17 00:00:00 2001 From: Ludo Date: Fri, 7 Oct 2022 21:39:03 +0200 Subject: [PATCH 38/41] update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6de61242..1013e67b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file. ### BLUEPRINTS +- [[#818](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/818)] Example wordpress ([skalolazka](https://github.com/skalolazka)) - [[#861](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/861)] Leverage new shared VPC project config defaults across the repo ([juliocc](https://github.com/juliocc)) - [[#854](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/854)] Added an example of a Nginx reverse proxy cluster using RMIGs ([rosmo](https://github.com/rosmo)) - [[#850](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/850)] Made sample alert creation optional ([maunope](https://github.com/maunope)) @@ -21,10 +22,12 @@ All notable changes to this project will be documented in this file. ### DOCUMENTATION +- [[#863](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/863)] Fabric vs CFT doc ([ludoo](https://github.com/ludoo)) - [[#806](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/806)] Companion Guide ([ajlopezn](https://github.com/ajlopezn)) ### FAST +- [[#865](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/865)] Enable FAST 00-cicd provider test ([ludoo](https://github.com/ludoo)) - [[#861](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/861)] Leverage new shared VPC project config defaults across the repo ([juliocc](https://github.com/juliocc)) - [[#858](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/858)] Default gcp-support to gcp-devops ([juliocc](https://github.com/juliocc)) - [[#842](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/842)] Comment redundant role in bootstrap stage, align IAM.md files, improve IAM tool ([ludoo](https://github.com/ludoo)) @@ -35,6 +38,7 @@ All notable changes to this project will be documented in this file. ### MODULES +- [[#860](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/860)] **incompatible change:** Refactor compute-vm for Terraform 1.3 ([ludoo](https://github.com/ludoo)) - [[#861](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/861)] Leverage new shared VPC project config defaults across the repo ([juliocc](https://github.com/juliocc)) - [[#859](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/859)] Make project shared VPC fields optional ([juliocc](https://github.com/juliocc)) - [[#853](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/853)] Fixes NVA issue when health checks are not enabled ([sruffilli](https://github.com/sruffilli)) @@ -54,6 +58,8 @@ All notable changes to this project will be documented in this file. ### TOOLS +- [[#865](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/865)] Enable FAST 00-cicd provider test ([ludoo](https://github.com/ludoo)) +- [[#864](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/864)] **incompatible change:** Bump terraform required version ([ludoo](https://github.com/ludoo)) - [[#842](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/842)] Comment redundant role in bootstrap stage, align IAM.md files, improve IAM tool ([ludoo](https://github.com/ludoo)) - [[#811](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/811)] Fix changelog generator ([ludoo](https://github.com/ludoo)) - [[#810](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/810)] Fully recursive e2e test runner for examples ([juliocc](https://github.com/juliocc)) From 67577ee80ba7b306ce6a8f07553cbae0d202b8be Mon Sep 17 00:00:00 2001 From: Simone Ruffilli Date: Sun, 9 Oct 2022 15:41:56 +0200 Subject: [PATCH 39/41] Update README.md --- modules/cloud-config-container/simple-nva/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cloud-config-container/simple-nva/README.md b/modules/cloud-config-container/simple-nva/README.md index 7fbb109d..5014e9a3 100644 --- a/modules/cloud-config-container/simple-nva/README.md +++ b/modules/cloud-config-container/simple-nva/README.md @@ -1,4 +1,4 @@ -# Google Cloud DNS Module +# Google Simple NVA Module This module allows for the creation of a NVA (Network Virtual Appliance) to be used for experiments and as a stub for future appliances deployment. From b5ee78c22d2fe6bb78f3e12f39e9311e6fe0226b Mon Sep 17 00:00:00 2001 From: Simone Ruffilli Date: Sun, 9 Oct 2022 17:26:54 +0200 Subject: [PATCH 40/41] Update ipprefix_by_netmask.sh (#866) When code was moved from terraform template to separate file, "$$" (used to print $ on a tf template) was wrongly left behind. --- .../simple-nva/files/ipprefix_by_netmask.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cloud-config-container/simple-nva/files/ipprefix_by_netmask.sh b/modules/cloud-config-container/simple-nva/files/ipprefix_by_netmask.sh index 405c1649..a1c69822 100644 --- a/modules/cloud-config-container/simple-nva/files/ipprefix_by_netmask.sh +++ b/modules/cloud-config-container/simple-nva/files/ipprefix_by_netmask.sh @@ -15,7 +15,7 @@ # limitations under the License. # https://stackoverflow.com/questions/50413579/bash-convert-netmask-in-cidr-notation -c=0 x=0$(printf '%o' $${1//./ }) +c=0 x=0$(printf '%o' ${1//./ }) while [ $x -gt 0 ]; do let c+=$((x % 2)) 'x>>=1' done From 674deb1c4fe36b322e7c706fc5069f2a2df79767 Mon Sep 17 00:00:00 2001 From: Simone Ruffilli Date: Mon, 10 Oct 2022 09:16:28 +0200 Subject: [PATCH 41/41] FAST: Replace NVAs in 02-networking-nva with COS-based VMs (#867) --- fast/stages/02-networking-nva/README.md | 2 +- fast/stages/02-networking-nva/nva.tf | 254 +++++++------------- fast/stages/02-networking-nva/spoke-dev.tf | 8 +- fast/stages/02-networking-nva/spoke-prod.tf | 8 +- 4 files changed, 97 insertions(+), 175 deletions(-) diff --git a/fast/stages/02-networking-nva/README.md b/fast/stages/02-networking-nva/README.md index 91071b2c..84c236cf 100644 --- a/fast/stages/02-networking-nva/README.md +++ b/fast/stages/02-networking-nva/README.md @@ -352,7 +352,7 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS | [landing.tf](./landing.tf) | Landing VPC and related resources. | net-cloudnat · net-vpc · net-vpc-firewall · project | | | [main.tf](./main.tf) | Networking folder and hierarchical policy. | folder | | | [monitoring.tf](./monitoring.tf) | Network monitoring dashboards. | | google_monitoring_dashboard | -| [nva.tf](./nva.tf) | None | compute-mig · compute-vm · net-ilb | | +| [nva.tf](./nva.tf) | None | compute-mig · compute-vm · simple-nva | | | [outputs.tf](./outputs.tf) | Module outputs. | | google_storage_bucket_object · local_file | | [spoke-dev.tf](./spoke-dev.tf) | Dev spoke VPC and related resources. | net-vpc · net-vpc-firewall · net-vpc-peering · project | google_project_iam_binding | | [spoke-prod.tf](./spoke-prod.tf) | Production spoke VPC and related resources. | net-vpc · net-vpc-firewall · net-vpc-peering · project | google_project_iam_binding | diff --git a/fast/stages/02-networking-nva/nva.tf b/fast/stages/02-networking-nva/nva.tf index b7dd6988..4e70d02f 100644 --- a/fast/stages/02-networking-nva/nva.tf +++ b/fast/stages/02-networking-nva/nva.tf @@ -15,181 +15,97 @@ */ locals { - _subnets = var.data_dir == null ? tomap({}) : { - for f in fileset("${var.data_dir}/subnets", "**/*.yaml") : - trimsuffix(basename(f), ".yaml") => yamldecode(file("${var.data_dir}/subnets/${f}")) - } - subnets = merge( - { for k, v in local._subnets : "${k}-cidr" => v.ip_cidr_range }, - { for k, v in local._subnets : "${k}-gw" => cidrhost(v.ip_cidr_range, 1) } - ) -} - -# europe-west1 - -module "nva-template-ew1" { - source = "../../../modules/compute-vm" - project_id = module.landing-project.project_id - name = "nva-template" - zone = "europe-west1-b" - tags = ["nva"] - can_ip_forward = true - network_interfaces = [ + # routing_config should be aligned to the NVA network interfaces - i.e. + # local.routing_config[0] sets up the first interface, and so on. + routing_config = [ { - network = module.landing-untrusted-vpc.self_link - subnetwork = module.landing-untrusted-vpc.subnet_self_links["europe-west1/landing-untrusted-default-ew1"] + name = "untrusted" + routes = [ + var.custom_adv.gcp_landing_untrusted_ew1, + var.custom_adv.gcp_landing_untrusted_ew4, + ] }, { - network = module.landing-trusted-vpc.self_link - subnetwork = module.landing-trusted-vpc.subnet_self_links["europe-west1/landing-trusted-default-ew1"] - } + name = "trusted" + routes = [ + var.custom_adv.gcp_dev_ew1, + var.custom_adv.gcp_dev_ew4, + var.custom_adv.gcp_landing_trusted_ew1, + var.custom_adv.gcp_landing_trusted_ew4, + var.custom_adv.gcp_prod_ew1, + var.custom_adv.gcp_prod_ew4, + ] + }, ] - boot_disk = { - image = "projects/debian-cloud/global/images/family/debian-10" + nva_locality = { + europe-west1-b = { region = "europe-west1", trigram = "ew1", zone = "b" }, + europe-west1-c = { region = "europe-west1", trigram = "ew1", zone = "c" }, + europe-west4-b = { region = "europe-west4", trigram = "ew4", zone = "b" }, + europe-west4-c = { region = "europe-west4", trigram = "ew4", zone = "c" }, } + +} + +# NVA config +module "nva-cloud-config" { + source = "../../../modules/cloud-config-container/simple-nva" + enable_health_checks = true + network_interfaces = local.routing_config +} + +module "nva-template" { + for_each = local.nva_locality + source = "../../../modules/compute-vm" + project_id = module.landing-project.project_id + name = "nva-template-${each.value.trigram}-${each.value.zone}" + zone = "${each.value.region}-${each.value.zone}" + instance_type = "e2-standard-2" + tags = ["nva"] create_template = true - instance_type = "f1-micro" - options = { - spot = true - termination_action = "STOP" - } - metadata = { - startup-script = templatefile( - "${path.module}/data/nva-startup-script.tftpl", - { - dev-default-ew1-cidr = local.subnets.dev-default-ew1-cidr - dev-default-ew4-cidr = local.subnets.dev-default-ew4-cidr - gateway-trusted = local.subnets.landing-trusted-default-ew1-gw - gateway-untrusted = local.subnets.landing-untrusted-default-ew1-gw - landing-trusted-other-region = local.subnets.landing-trusted-default-ew4-cidr - landing-untrusted-other-region = local.subnets.landing-untrusted-default-ew4-cidr - onprem-main-cidr = var.onprem_cidr.main - prod-default-ew1-cidr = local.subnets.prod-default-ew1-cidr - prod-default-ew4-cidr = local.subnets.prod-default-ew4-cidr - } - ) - } -} - -module "nva-mig-ew1" { - source = "../../../modules/compute-mig" - project_id = module.landing-project.project_id - regional = true - location = "europe-west1" - name = "nva-ew1" - target_size = 2 - auto_healing_policies = { - health_check = module.nva-mig-ew1.health_check.self_link - initial_delay_sec = 30 - } - health_check_config = { - type = "tcp" - check = { port = 22 } - config = {} - logging = true - } - default_version = { - instance_template = module.nva-template-ew1.template.self_link - name = "default" - } -} - -module "ilb-nva-untrusted-ew1" { - source = "../../../modules/net-ilb" - project_id = module.landing-project.project_id - region = "europe-west1" - name = "ilb-nva-untrusted-ew1" - service_label = var.prefix - global_access = true - network = module.landing-untrusted-vpc.self_link - subnetwork = module.landing-untrusted-vpc.subnet_self_links["europe-west1/landing-untrusted-default-ew1"] - backends = [{ - failover = false - group = module.nva-mig-ew1.group_manager.instance_group - balancing_mode = "CONNECTION" - }] - health_check_config = { - type = "tcp", check = { port = 22 }, config = {}, logging = false - } -} - -module "ilb-nva-trusted-ew1" { - source = "../../../modules/net-ilb" - project_id = module.landing-project.project_id - region = "europe-west1" - name = "ilb-nva-trusted-ew1" - service_label = var.prefix - global_access = true - network = module.landing-trusted-vpc.self_link - subnetwork = module.landing-trusted-vpc.subnet_self_links["europe-west1/landing-trusted-default-ew1"] - backends = [{ - failover = false - group = module.nva-mig-ew1.group_manager.instance_group - balancing_mode = "CONNECTION" - }] - health_check_config = { - type = "tcp", check = { port = 22 }, config = {}, logging = false - } -} - -# europe-west4 - -module "nva-template-ew4" { - source = "../../../modules/compute-vm" - project_id = module.landing-project.project_id - name = "nva-template" - zone = "europe-west4-a" - tags = ["nva"] - can_ip_forward = true + can_ip_forward = true network_interfaces = [ { network = module.landing-untrusted-vpc.self_link - subnetwork = module.landing-untrusted-vpc.subnet_self_links["europe-west4/landing-untrusted-default-ew4"] + subnetwork = module.landing-untrusted-vpc.subnet_self_links["${each.value.region}/landing-untrusted-default-${each.value.trigram}"] nat = false addresses = null }, { network = module.landing-trusted-vpc.self_link - subnetwork = module.landing-trusted-vpc.subnet_self_links["europe-west4/landing-trusted-default-ew4"] + subnetwork = module.landing-trusted-vpc.subnet_self_links["${each.value.region}/landing-trusted-default-${each.value.trigram}"] nat = false addresses = null } ] boot_disk = { - image = "projects/debian-cloud/global/images/family/debian-10" - type = "pd-balanced" + image = "projects/cos-cloud/global/images/family/cos-stable" size = 10 + type = "pd-balanced" + } + options = { + allow_stopping_for_update = true + deletion_protection = false + spot = true + termination_action = "STOP" } - create_template = true metadata = { - startup-script = templatefile( - "${path.module}/data/nva-startup-script.tftpl", - { - dev-default-ew1-cidr = local.subnets.dev-default-ew1-cidr - dev-default-ew4-cidr = local.subnets.dev-default-ew4-cidr - gateway-trusted = local.subnets.landing-trusted-default-ew4-gw - gateway-untrusted = local.subnets.landing-untrusted-default-ew4-gw - landing-trusted-other-region = local.subnets.landing-trusted-default-ew1-cidr - landing-untrusted-other-region = local.subnets.landing-untrusted-default-ew1-cidr - onprem-main-cidr = var.onprem_cidr.main - prod-default-ew1-cidr = local.subnets.prod-default-ew1-cidr - prod-default-ew4-cidr = local.subnets.prod-default-ew4-cidr - } - ) + user-data = module.nva-cloud-config.cloud_config } } -module "nva-mig-ew4" { +module "nva-mig" { + for_each = local.nva_locality source = "../../../modules/compute-mig" project_id = module.landing-project.project_id regional = true - location = "europe-west4" - name = "nva-ew4" - target_size = 2 - auto_healing_policies = { - health_check = module.nva-mig-ew4.health_check.self_link - initial_delay_sec = 30 - } + location = each.value.region + name = "nva-cos-${each.value.trigram}-${each.value.zone}" + target_size = 1 + # FIXME: cycle + # auto_healing_policies = { + # health_check = module.nva-mig[each.key].health_check.self_link + # initial_delay_sec = 30 + # } health_check_config = { type = "tcp" check = { port = 22 } @@ -197,45 +113,51 @@ module "nva-mig-ew4" { logging = true } default_version = { - instance_template = module.nva-template-ew4.template.self_link + instance_template = module.nva-template[each.key].template.self_link name = "default" } } -module "ilb-nva-untrusted-ew4" { +module "ilb-nva-untrusted" { + for_each = { for l in local.nva_locality : l.region => l.trigram... } source = "../../../modules/net-ilb" project_id = module.landing-project.project_id - region = "europe-west4" - name = "ilb-nva-untrusted-ew4" + region = each.key + name = "nva-untrusted-${each.value.0}" service_label = var.prefix global_access = true network = module.landing-untrusted-vpc.self_link - subnetwork = module.landing-untrusted-vpc.subnet_self_links["europe-west4/landing-untrusted-default-ew4"] - backends = [{ - failover = false - group = module.nva-mig-ew4.group_manager.instance_group - balancing_mode = "CONNECTION" - }] + subnetwork = module.landing-untrusted-vpc.subnet_self_links["${each.key}/landing-untrusted-default-${each.value.0}"] + backends = [for key, _ in local.nva_locality : + { + failover = false + group = module.nva-mig[key].group_manager.instance_group + balancing_mode = "CONNECTION" + } if local.nva_locality[key].region == each.key] health_check_config = { type = "tcp", check = { port = 22 }, config = {}, logging = false } } -module "ilb-nva-trusted-ew4" { + +module "ilb-nva-trusted" { + for_each = { for l in local.nva_locality : l.region => l.trigram... } source = "../../../modules/net-ilb" project_id = module.landing-project.project_id - region = "europe-west4" - name = "ilb-nva-trusted-ew4" + region = each.key + name = "nva-trusted-${each.value.0}" service_label = var.prefix global_access = true network = module.landing-trusted-vpc.self_link - subnetwork = module.landing-trusted-vpc.subnet_self_links["europe-west4/landing-trusted-default-ew4"] - backends = [{ - failover = false - group = module.nva-mig-ew4.group_manager.instance_group - balancing_mode = "CONNECTION" - }] + subnetwork = module.landing-trusted-vpc.subnet_self_links["${each.key}/landing-trusted-default-${each.value.0}"] + backends = [for key, _ in local.nva_locality : + { + failover = false + group = module.nva-mig[key].group_manager.instance_group + balancing_mode = "CONNECTION" + } if local.nva_locality[key].region == each.key] health_check_config = { type = "tcp", check = { port = 22 }, config = {}, logging = false } } + diff --git a/fast/stages/02-networking-nva/spoke-dev.tf b/fast/stages/02-networking-nva/spoke-dev.tf index ba11cd8e..3499206f 100644 --- a/fast/stages/02-networking-nva/spoke-dev.tf +++ b/fast/stages/02-networking-nva/spoke-dev.tf @@ -72,28 +72,28 @@ module "dev-spoke-vpc" { priority = 1000 tags = ["ew1"] next_hop_type = "ilb" - next_hop = module.ilb-nva-trusted-ew1.forwarding_rule_address + next_hop = module.ilb-nva-trusted["europe-west1"].forwarding_rule_address } nva-ew4-to-ew4 = { dest_range = "0.0.0.0/0" priority = 1000 tags = ["ew4"] next_hop_type = "ilb" - next_hop = module.ilb-nva-trusted-ew4.forwarding_rule_address + next_hop = module.ilb-nva-trusted["europe-west4"].forwarding_rule_address } nva-ew1-to-ew4 = { dest_range = "0.0.0.0/0" priority = 1001 tags = ["ew1"] next_hop_type = "ilb" - next_hop = module.ilb-nva-trusted-ew4.forwarding_rule_address + next_hop = module.ilb-nva-trusted["europe-west4"].forwarding_rule_address } nva-ew4-to-ew1 = { dest_range = "0.0.0.0/0" priority = 1001 tags = ["ew4"] next_hop_type = "ilb" - next_hop = module.ilb-nva-trusted-ew1.forwarding_rule_address + next_hop = module.ilb-nva-trusted["europe-west1"].forwarding_rule_address } } } diff --git a/fast/stages/02-networking-nva/spoke-prod.tf b/fast/stages/02-networking-nva/spoke-prod.tf index 7150195e..6a0c26c8 100644 --- a/fast/stages/02-networking-nva/spoke-prod.tf +++ b/fast/stages/02-networking-nva/spoke-prod.tf @@ -72,28 +72,28 @@ module "prod-spoke-vpc" { priority = 1000 tags = ["ew1"] next_hop_type = "ilb" - next_hop = module.ilb-nva-trusted-ew1.forwarding_rule_address + next_hop = module.ilb-nva-trusted["europe-west1"].forwarding_rule_address } nva-ew4-to-ew4 = { dest_range = "0.0.0.0/0" priority = 1000 tags = ["ew4"] next_hop_type = "ilb" - next_hop = module.ilb-nva-trusted-ew4.forwarding_rule_address + next_hop = module.ilb-nva-trusted["europe-west4"].forwarding_rule_address } nva-ew1-to-ew4 = { dest_range = "0.0.0.0/0" priority = 1001 tags = ["ew1"] next_hop_type = "ilb" - next_hop = module.ilb-nva-trusted-ew4.forwarding_rule_address + next_hop = module.ilb-nva-trusted["europe-west4"].forwarding_rule_address } nva-ew4-to-ew1 = { dest_range = "0.0.0.0/0" priority = 1001 tags = ["ew4"] next_hop_type = "ilb" - next_hop = module.ilb-nva-trusted-ew1.forwarding_rule_address + next_hop = module.ilb-nva-trusted["europe-west1"].forwarding_rule_address } } }