From 166c9574a1e3a4b97459755840352d561fbcd9e2 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Mon, 4 Apr 2022 17:01:24 +0200 Subject: [PATCH 01/28] Multi-region Cloud-SQL example --- .../cloudsql-multiregion/README.md | 24 +++++++++ .../cloudsql-multiregion/main.tf | 54 +++++++++++++++++++ .../cloudsql-multiregion/outputs.tf | 30 +++++++++++ .../cloudsql-multiregion/variables.tf | 43 +++++++++++++++ modules/cloudsql-instance/main.tf | 39 ++++++++------ 5 files changed, 173 insertions(+), 17 deletions(-) create mode 100644 examples/data-solutions/cloudsql-multiregion/README.md create mode 100644 examples/data-solutions/cloudsql-multiregion/main.tf create mode 100644 examples/data-solutions/cloudsql-multiregion/outputs.tf create mode 100644 examples/data-solutions/cloudsql-multiregion/variables.tf diff --git a/examples/data-solutions/cloudsql-multiregion/README.md b/examples/data-solutions/cloudsql-multiregion/README.md new file mode 100644 index 00000000..dfb0b71e --- /dev/null +++ b/examples/data-solutions/cloudsql-multiregion/README.md @@ -0,0 +1,24 @@ +# Cloud SQL instance with multi-region read replicas + +TBD + + + +## Variables + +| name | description | type | required | default | +|---|---|:---:|:---:|:---:| +| [prefix](variables.tf#L17) | Unique prefix used for resource names. Not used for project if 'project_create' is null. | string | ✓ | | +| [project_id](variables.tf#L31) | Project id, references existing project if `project_create` is null. | string | ✓ | | +| [regions](variables.tf#L36) | Map of instance_name => location where instances will be deployed. | map(string) | ✓ | | +| [project_create](variables.tf#L22) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | | null | + +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| [connection_names](outputs.tf#L17) | Connection name of each instance. | | +| [ips](outputs.tf#L22) | IP address of each instance. | | +| [project_id](outputs.tf#L27) | ID of the project containing all the instances. | | + + diff --git a/examples/data-solutions/cloudsql-multiregion/main.tf b/examples/data-solutions/cloudsql-multiregion/main.tf new file mode 100644 index 00000000..116d9d5f --- /dev/null +++ b/examples/data-solutions/cloudsql-multiregion/main.tf @@ -0,0 +1,54 @@ +/** + * 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. + */ + +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 + services = [ + "servicenetworking.googleapis.com", + ] + +} + +module "vpc" { + source = "../../../modules/net-vpc" + project_id = module.project.project_id + name = "vpc" + psa_config = { + ranges = { cloud-sql = "10.60.0.0/16" } + routes = null + } +} + +module "db" { + source = "../../../modules/cloudsql-instance" + project_id = module.project.project_id + network = module.vpc.self_link + name = "db" + region = var.regions.primary + database_version = "POSTGRES_13" + tier = "db-g1-small" + + replicas = { + for name, region in var.regions : + name => region + if name != "primary" + } +} diff --git a/examples/data-solutions/cloudsql-multiregion/outputs.tf b/examples/data-solutions/cloudsql-multiregion/outputs.tf new file mode 100644 index 00000000..fddbb5e3 --- /dev/null +++ b/examples/data-solutions/cloudsql-multiregion/outputs.tf @@ -0,0 +1,30 @@ +/** + * 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. + */ + +output "connection_names" { + description = "Connection name of each instance." + value = module.db.connection_names +} + +output "ips" { + description = "IP address of each instance." + value = module.db.ips +} + +output "project_id" { + description = "ID of the project containing all the instances." + value = module.project.project_id +} diff --git a/examples/data-solutions/cloudsql-multiregion/variables.tf b/examples/data-solutions/cloudsql-multiregion/variables.tf new file mode 100644 index 00000000..5e922c41 --- /dev/null +++ b/examples/data-solutions/cloudsql-multiregion/variables.tf @@ -0,0 +1,43 @@ +/** + * 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 "regions" { + description = "Map of instance_name => location where instances will be deployed." + type = map(string) + validation { + condition = contains(keys(var.regions), "primary") + error_message = "Regions map must contain `primary` as a key." + } +} diff --git a/modules/cloudsql-instance/main.tf b/modules/cloudsql-instance/main.tf index f50121ab..0f817b29 100644 --- a/modules/cloudsql-instance/main.tf +++ b/modules/cloudsql-instance/main.tf @@ -19,6 +19,10 @@ locals { is_mysql = can(regex("^MYSQL", var.database_version)) has_replicas = try(length(var.replicas) > 0, false) + // Enable backup if the user asks for it or if the user is deploying + // MySQL with replicas + enable_backup = var.backup_configuration.enabled || (local.is_mysql && local.has_replicas) + users = { for user, password in coalesce(var.users, {}) : (user) => ( @@ -65,24 +69,25 @@ resource "google_sql_database_instance" "primary" { } } - backup_configuration { - // Enable backup if the user asks for it or if the user is - // deploying MySQL with replicas - enabled = var.backup_configuration.enabled || (local.is_mysql && local.has_replicas) + dynamic "backup_configuration" { + for_each = local.enable_backup ? { 1 = 1 } : {} + content { + enabled = true - // enable binary log if the user asks for it or we have replicas, - // but only form MySQL - binary_log_enabled = ( - local.is_mysql - ? var.backup_configuration.binary_log_enabled || local.has_replicas - : null - ) - start_time = var.backup_configuration.start_time - location = var.backup_configuration.location - transaction_log_retention_days = var.backup_configuration.log_retention_days - backup_retention_settings { - retained_backups = var.backup_configuration.retention_count - retention_unit = "COUNT" + // enable binary log if the user asks for it or we have replicas, + // but only for MySQL + binary_log_enabled = ( + local.is_mysql + ? var.backup_configuration.binary_log_enabled || local.has_replicas + : null + ) + start_time = var.backup_configuration.start_time + location = var.backup_configuration.location + transaction_log_retention_days = var.backup_configuration.log_retention_days + backup_retention_settings { + retained_backups = var.backup_configuration.retention_count + retention_unit = "COUNT" + } } } From 40ca7c5d9c4a2147dfc7a77b04f938c3f840f433 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Mon, 4 Apr 2022 17:06:12 +0200 Subject: [PATCH 02/28] Extract PSA range as a variable --- examples/data-solutions/cloudsql-multiregion/main.tf | 3 +-- examples/data-solutions/cloudsql-multiregion/variables.tf | 6 ++++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/examples/data-solutions/cloudsql-multiregion/main.tf b/examples/data-solutions/cloudsql-multiregion/main.tf index 116d9d5f..f2bbf47e 100644 --- a/examples/data-solutions/cloudsql-multiregion/main.tf +++ b/examples/data-solutions/cloudsql-multiregion/main.tf @@ -24,7 +24,6 @@ module "project" { services = [ "servicenetworking.googleapis.com", ] - } module "vpc" { @@ -32,7 +31,7 @@ module "vpc" { project_id = module.project.project_id name = "vpc" psa_config = { - ranges = { cloud-sql = "10.60.0.0/16" } + ranges = { cloud-sql = var.cloudsql_psa_range } routes = null } } diff --git a/examples/data-solutions/cloudsql-multiregion/variables.tf b/examples/data-solutions/cloudsql-multiregion/variables.tf index 5e922c41..b169d81d 100644 --- a/examples/data-solutions/cloudsql-multiregion/variables.tf +++ b/examples/data-solutions/cloudsql-multiregion/variables.tf @@ -14,6 +14,12 @@ * limitations under the License. */ +variable "cloudsql_psa_range" { + description = "Range used for the Private Service Access." + type = string + default = "10.60.0.0/16" +} + variable "prefix" { description = "Unique prefix used for resource names. Not used for project if 'project_create' is null." type = string From e58c9e6ed5c876c29f3304e855c415a4311919e1 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Mon, 4 Apr 2022 17:13:21 +0200 Subject: [PATCH 03/28] Extract DB tier to variable. --- .../data-solutions/cloudsql-multiregion/README.md | 11 ++++++----- examples/data-solutions/cloudsql-multiregion/main.tf | 2 +- .../data-solutions/cloudsql-multiregion/variables.tf | 6 ++++++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/examples/data-solutions/cloudsql-multiregion/README.md b/examples/data-solutions/cloudsql-multiregion/README.md index dfb0b71e..9da4648d 100644 --- a/examples/data-solutions/cloudsql-multiregion/README.md +++ b/examples/data-solutions/cloudsql-multiregion/README.md @@ -1,17 +1,18 @@ # Cloud SQL instance with multi-region read replicas TBD - ## Variables | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [prefix](variables.tf#L17) | Unique prefix used for resource names. Not used for project if 'project_create' is null. | string | ✓ | | -| [project_id](variables.tf#L31) | Project id, references existing project if `project_create` is null. | string | ✓ | | -| [regions](variables.tf#L36) | Map of instance_name => location where instances will be deployed. | map(string) | ✓ | | -| [project_create](variables.tf#L22) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | | null | +| [prefix](variables.tf#L23) | Unique prefix used for resource names. Not used for project if 'project_create' is null. | string | ✓ | | +| [project_id](variables.tf#L37) | Project id, references existing project if `project_create` is null. | string | ✓ | | +| [regions](variables.tf#L42) | Map of instance_name => location where instances will be deployed. | map(string) | ✓ | | +| [cloudsql_psa_range](variables.tf#L17) | Range used for the Private Service Access. | string | | "10.60.0.0/16" | +| [project_create](variables.tf#L28) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | | null | +| [tier](variables.tf#L51) | The machine type to use for the instances. See See https://cloud.google.com/sql/docs/postgres/create-instance#machine-types. | string | | "db-g1-small" | ## Outputs diff --git a/examples/data-solutions/cloudsql-multiregion/main.tf b/examples/data-solutions/cloudsql-multiregion/main.tf index f2bbf47e..2c0e81d1 100644 --- a/examples/data-solutions/cloudsql-multiregion/main.tf +++ b/examples/data-solutions/cloudsql-multiregion/main.tf @@ -43,7 +43,7 @@ module "db" { name = "db" region = var.regions.primary database_version = "POSTGRES_13" - tier = "db-g1-small" + tier = var.tier replicas = { for name, region in var.regions : diff --git a/examples/data-solutions/cloudsql-multiregion/variables.tf b/examples/data-solutions/cloudsql-multiregion/variables.tf index b169d81d..b49d91f0 100644 --- a/examples/data-solutions/cloudsql-multiregion/variables.tf +++ b/examples/data-solutions/cloudsql-multiregion/variables.tf @@ -47,3 +47,9 @@ variable "regions" { error_message = "Regions map must contain `primary` as a key." } } + +variable "tier" { + description = "The machine type to use for the instances. See See https://cloud.google.com/sql/docs/postgres/create-instance#machine-types." + type = string + default = "db-g1-small" +} From a4d59a250b714d6258da7ed251c8f7d05956b5a6 Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Mon, 11 Apr 2022 18:14:59 +0200 Subject: [PATCH 04/28] Improve README --- .../cloudsql-multiregion/README.md | 56 ++++++++++++++++-- .../cloudsql-multiregion/backend.tf.sample | 30 ++++++++++ .../cloudsql-multiregion/diagram.png | Bin 0 -> 23721 bytes .../cloudsql-multiregion/main.tf | 4 +- .../terraform.tfvars.sample | 2 + .../cloudsql-multiregion/variables.tf | 10 ++++ 6 files changed, 94 insertions(+), 8 deletions(-) create mode 100644 examples/data-solutions/cloudsql-multiregion/backend.tf.sample create mode 100644 examples/data-solutions/cloudsql-multiregion/diagram.png create mode 100644 examples/data-solutions/cloudsql-multiregion/terraform.tfvars.sample diff --git a/examples/data-solutions/cloudsql-multiregion/README.md b/examples/data-solutions/cloudsql-multiregion/README.md index 9da4648d..9e5b280b 100644 --- a/examples/data-solutions/cloudsql-multiregion/README.md +++ b/examples/data-solutions/cloudsql-multiregion/README.md @@ -1,18 +1,62 @@ # Cloud SQL instance with multi-region read replicas -TBD +This example creates the [Cloud SQL instance](https://cloud.google.com/sql) with multi-reagion read replica solution described in the [`Cloud SQL for PostgreSQL disaster recovery`](https://cloud.google.com/architecture/cloud-sql-postgres-disaster-recovery-complete-failover-fallback) article. + +The solution is resiliant to a regional outage. To get familiar with the procedure needed in the unfortunate case of a disaster recovery, we suggest to follow steps described in the [`Simulating a disaster (region outage)`](https://cloud.google.com/architecture/cloud-sql-postgres-disaster-recovery-complete-failover-fallback#phase-2) article. + +The solution will use: +- Postgre SQL instance with Private IP + +This is the high level diagram: + +![Cloud SQL multi-region.](diagram.png "Cloud SQL multi-region") + +## Move to real use case consideration +In the example we implemented some compromise to keep the example minimal and easy to read. On a real word use case, you may evaluate the option to: + - Configure a Shared-VPC + - Use VPC-SC to mitigate data exfiltration + +## Deploy your enviroment + +We assume the identiy running the following steps has the following role: + - `resourcemanager.projectCreator` in case a new project will be created. + - `owner` on the project in case you use an existing project. + +Run Terraform init: + +``` +$ terraform init +``` + +Configure the Terraform variable in your `terraform.tfvars` file. You need to spefify at least the following variables: + +``` +data_eng_principals = ["user:data-eng@domain.com"] +project_id = "datalake-001" +prefix = "prefix" +``` + +You can run now: + +``` +$ terraform apply +``` + +You should see the output of the Terraform script with resources created and some command pre-created for you to run the example following steps below. +TBC ## Variables | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [prefix](variables.tf#L23) | Unique prefix used for resource names. Not used for project if 'project_create' is null. | string | ✓ | | -| [project_id](variables.tf#L37) | Project id, references existing project if `project_create` is null. | string | ✓ | | -| [regions](variables.tf#L42) | Map of instance_name => location where instances will be deployed. | map(string) | ✓ | | +| [prefix](variables.tf#L29) | Unique prefix used for resource names. Not used for project if 'project_create' is null. | string | ✓ | | +| [project_id](variables.tf#L43) | Project id, references existing project if `project_create` is null. | string | ✓ | | +| [regions](variables.tf#L48) | Map of instance_name => location where instances will be deployed. | map(string) | ✓ | | | [cloudsql_psa_range](variables.tf#L17) | Range used for the Private Service Access. | string | | "10.60.0.0/16" | -| [project_create](variables.tf#L28) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | | null | -| [tier](variables.tf#L51) | The machine type to use for the instances. See See https://cloud.google.com/sql/docs/postgres/create-instance#machine-types. | string | | "db-g1-small" | +| [database_version](variables.tf#L23) | Database type and version to create. | string | | "POSTGRES_13" | +| [project_create](variables.tf#L34) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | | null | +| [tier](variables.tf#L57) | The machine type to use for the instances. See See https://cloud.google.com/sql/docs/postgres/create-instance#machine-types. | string | | "db-g1-small" | ## Outputs diff --git a/examples/data-solutions/cloudsql-multiregion/backend.tf.sample b/examples/data-solutions/cloudsql-multiregion/backend.tf.sample new file mode 100644 index 00000000..49a0883d --- /dev/null +++ b/examples/data-solutions/cloudsql-multiregion/backend.tf.sample @@ -0,0 +1,30 @@ +# 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 +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# The `impersonate_service_account` option require the identity launching terraform +# role `roles/iam.serviceAccountTokenCreator` on the Service Account specified. + +terraform { + backend "gcs" { + bucket = "BUCKET_NAME" + prefix = "PREFIX" + impersonate_service_account = "SERVICE_ACCOUNT@PROJECT_ID.iam.gserviceaccount.com" + } +} +provider "google" { + impersonate_service_account = "SERVICE_ACCOUNT@PROJECT_ID.iam.gserviceaccount.com" +} +provider "google-beta" { + impersonate_service_account = "SERVICE_ACCOUNT@PROJECT_ID.iam.gserviceaccount.com" +} \ No newline at end of file diff --git a/examples/data-solutions/cloudsql-multiregion/diagram.png b/examples/data-solutions/cloudsql-multiregion/diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..6148a53ee3391753fdae21b1c6e729761e220fb9 GIT binary patch literal 23721 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

XF%V}J zZ{yLOpcLNKim-L9NKApzmZ|$)x%Ze|UY;+nxv;^v_@K_WPqrp0R$~$zmlO6r zsZG6*_o^@kISN;-E>c;nR!J~5!c8DtbY#i(0&7+2(Qb#>LN zZ~(Ye^dctXKBuis+_Nl5;3TFr&(C)Z=HAd;sH<<_Oe@31CDSnQIXTVs^{LEE&oNmx zFW6t~2gM?zWatV8H?Au=qN_|mN`xhL5e%m-% z>jGlw;eJQ_2>|s|fI7^9;0YyU0TKnj$McLP-Le+1D;aeBA6;ue-n0!+g0`{o`gEzz z)!|$f@FoCj4>@ejS!DPir~b*_av)k(hKGiP0ZX?5*s}uQ9z4a2?4vD^#q>q`-EROS z&&$UL$kw*?)I0B1vWllucJ|RzPO|&=tyK!kP(0D$Yc*;y@JU6Hw_e-HhX>bP(AN5P@!?{Nx95d~AgU;2e=%U$NNR8xix zD;iGjy}4#s{D!H87jJ@W7!xmhuO+F+^Lx`UAn)d$lXGs7vcl-3B^>Y6s*)QnXuC;i z`v>s8_Zw7z_V#lVAaA={I&M8rJHEfj7mw(z06c0&-^&g*nKNa*RPVF_$ftLRY?m25 zcb{Xgm8NwDa-2Q~zq^HhHhWrVd(fc)rUH^I(n%hYPrIldISDXZ0PVujdY!do{}kkR z9rQ0$M7%B!l@KSooeY8d)6*I>gJWYEp3Hs#(8*A_)O(z7!?`MWHsdt)Lqrs)yC@8JF8KtH(}U;4PtFS zB`>skY;Hb3wbYFWTboq>h|MX4|BYEA_S5O!!JXvChLq^Kvk}tM(2QEO53!+fld+Sh z`-_#D{JO%i>Cj4IFrUUF9vOP0K_th5)nlmxmRcL}S+p{;x`Yag1O){F6FSi`F@5@c z|5yqrX=!R|27S&P9R=r4B*n(UAU!y*Uw>^`PEAeS9nRXQ6%Y_ejEQj>LH%d}Mi!cQ zK+S`E=F>6!Jf4k+}Njxp1EV%PP z@54@P2CJ#yONrmSO}{L2-&5J_?XZs#ro%>q6~+Ua-UR~{qmgdns3KLpgj=JueXP?q zt?aOr5))ohEG(@2*2lH3AR8N-f94v~jaxPU`HU+1LEehwWJhQTWzNJoDduylW<<}h|hi*k9FVgTC-gC z5nq2a9BV8`b9%Xpc! zl1Ma@#aoN;Lxx(j06^V_)^0h1yWT(E8Z|WPLHHD^d*ufkw9#?MXBz>%T&goPIQY?F zS!7|V;kQ};;qj(Wi%=PrmR^v86xX|-I+QMJkT}PiI0r3^{CPSW?f85pT>*QK;eZ&y zX;uoG_(zWvEA@tED+yD*(~H#CkcdU!qs#Mn-&O%lrCM&)-=<`WwIR}0pIuY-ak_Vt zyNF5XJROtlB?~Qou~%cfq`lZa?O#KK!25q^pSpLVlr5(Y2}xZb#5rl6Y-fzO7k^<) z<+s0E7cSbQfH-Sp{oz$El8cE~lC4O!{5y1&4sm%4wXZbf=aSER=?9V0(^`~-BLE_WYHujw{Oa?J@Vo5jyK%}5u+ zPk^}WK*jHBL^1EAO$h^^|6SFOfsgJF^xjA_EIf+)QF`3yV%vmAuZ*>u{JxBi*4s)m zX!_YbK_xi02!5%(bg8Wu3R+O1+0yw=sP4TJ7yjaA4UN9*{U$vYm}f$Mld}qWp2J=@ z{jmTWy*>+gzA0So?d&@F>8LaFfam)ym{)btN4qho*=FykziZ9l;9wU1etW4gyLnpv zv}5+3#y}Zv**@wGG=6~cd${@p=0akj6Us8jLMB|4g?um8SmKY2gQ5@_;@2c)NEi;@7M(tED&!aN9L`vIL*C;ya~M;zOYqfKrjgNi=2%3iJo{Sq z+(XWogfFvH`?8p2r!4(iDmSXvFE>tpBysOl;tUtik%i&(d{D~RZvBz6E^uc=Nk9I7Bxj-Ux3`VmjmFMJqcXLCD0IHmQ_;++4Ud5W4<)zryqqbjEkzOxh zZGyJW&TOn-G`^NG{m3cb?C59B3dy#w`?x!WsTPnb)nRKGEf?J{kRL_ua5%MG9JPSX zASglt)T3YWhpjfgKWOLuB^4@@Y8JqQ4B_6N?IeFcppa&NblJDNNgVul52wcDml-1_ zG$hBvosZcb?EW#`8FfP%zH{ z?EINSxZq$HX4=7YV_fsSFS_b4&CdR3o`S`Rn$i#$8RF0?~Ebj%(X2N1y9XDSRf zX1tV4!!_4yeqkkCR0M>*hX|#A$FIAmI(LgPknRm-yWbAXrZ0unBZ}U{7WE^;tRbUO zlhFWO$B);1in-i{lW(Ghl}41zijKaFbcrlI(GXHbCx5|))_1*#{$uu@6$eWF-@EqS zUM&4hNW7^|Wsd%lBz{kYDJS(p{f%!Z`mTLpWB~YXA%9FwD>433J%7?49ryC;>ItYA zgAgQ<6v7$?22X%)qutY&R`vAs^cKrt3Z;x>bh0dF9Ri*}`u3^ax(0XvJ85LQu)M|f zKto2RcRe|MU-dFk=dq_~ccR!BS}W)$V9Nbhjd5%&r-N&HF+W_HL2zT1Zt3L1677B* zd&5}y4~xW!RGxV?b}O(9#A z7%(&R@V9ZpmI*&??;JLiTa;5k=U7QxNEh^O7OGgjD6F9B;Yk)*@_!U_ok2});T9Ws z=}iF`Rq7a%i1BN19LK8v@pm``hbV>sudi0g~o*pAUX|vGj2>3^=tePoI;d1yE;Z}C=O0hLAprCR5jfNL4x8^Im z@=_=@Lc#o6Uzg+6gv=%9pS9swce6O1A>6Ep=zYlWw4vblCPaIe$+r!gk*NK2b*O=s z97lBLI!F`Q1Vz02tB-DSDleHu-8oWi|x9a!i^SjHAbtaxY z#OVFZ4}MNpF?QIeyk0-G7IwG;2$*{Uo9!Gn8A|>=-~CKXi`)X&RnJpLfXrVVuoz&A zdRjEZ71O@5vJ!tq-3vpk^`6m(sH&<0+N^eh91b#rt*xyy&s(F}gaI$cpEp>4_1vpN zOo_}v3vMZdB4wU48cwu#3`(CANHTm6J@-Nw<;5TtM)~HcGGunYR6JYVoG*pryij74 zh^XP=n=iu>ru9Ksv38CO7r6%b3B^nt{94#&u=5(vOOhfSz-&C)vt<9_Vh zccq^=9e(UQVN=VlC@Yf?^j1#Zvv45=E&Z~3<3a%>({);gvm^1>1c%Tm-+pG$_i;H9 z%^Eb#Rvy$~&DoHhUpUXA#o239Vj_u?9#{DuLfx(yH`j~S?1)h(n)J7A7IY?DfyI)E zm`-QRV8SgsKoMudDf@b3LYjs^f)LQ|>F!Qf3hDg(`RtNP^E0eCKIZ`tRzAa!NGIaz zHtmZyNaQV}APWl%U^VdcEF1bRz!%Z>(Q~5qE$Fw>phUJm=oZd4D;piNfm^$C_V@Gz zgLM2T{G=#5+yZ!w6_Qm|GSD#>opXm1eMWBO_%VvL>#y~EJzXRxn~JR$Ms4q<#OHi- z$1=0Pj2FL7m_`d?C*2yuRM)wo>fQ+Jbt4T!|8&`H2i^W`b#)n;O_Fcd1Vm@EGao>Cuw6x2=XBZk_V!Q(USNXBz}$os@su0rq{QXh+)B^Ib{_DIXva zxBXr8`tT1#>AaaN3Ds4-8g|7IV>3xb{+5fe8LmH`a>p%{PY~ou%L)9*d~cFt+0dp{ zFY0ofmiJ_RB}mpKmR&d=I~@WTGthzpK5KB!Pd};qR(ZUBSLP?3g1h%HQqPSV zBFjXXX?YrpExFDgdAxrXfZLo9rguk9KUy?`y7J{_d89NqisBJE5Z~&qAdqEEc>#F@ z)!+jHg<7^G^LYUiKI8uO8#n*ez!w&_cXn74(P(Yz?%q>|>{FoS&w+}B1~n5CRItK^ z=Tb6KDQ%(;QyMGXQ(aYLf(Xmg8HAWbA9m!an^2XP7t&6eSx?Z)^Ud_< zbTBCy`R-S4j?r@8h`9SkpoDzJt;L3*4Z~-seCpxMJFWaQe z)C%t|hAEvE^?l}F!w3i;MD8%PqvdE**O1*N_`4vv88}7D$YgSnekQ3gXe-mQNb*sm zf|?oyupmCfDkn&F`0)WC5fl>KUJPZd2`@d z5?ZXgwsCz+*7b=1YdUE$81^Ac>G6qq=hBudhaYf;D_&*<_IKJy`^s@E7*8LT=mE}W zv>`OO>5*rMg$3yc9QTaz1B8vG#`hkX8;ZyX)oi}{M24%&bLm_FVn#L0|NcR#;pXc_ zZxa(TYkAoZX>M@AKtngGaB?3lb@lSv32x#|SYnj?72HG}k&~Y<(A@5lQRy2o^5w#RJ1j2NTMF!QaEVCr`>|5*PrZ>5Z-roW<|+&I}zpmt^! zQBPiQqTN^t%CV;GCCSOC8C<{1wr;D${paGAzI7*;m6dJ4d#WzSv^y%=o&9ETqH2ONtgUa4TvRrp*h-bp2^{7qK2p2TDvNL$ijdPn^9&CwB=E&F!@|M~U=Rjs209MgmFkdI+~cF$d@USoH6%tn{F5v7t=~*zt;Y)E4Z;)P!zGTwJ*YM`!ypLYw#CA$`aBLIl@kNQ&Yo7f2Bm2 z1#4A4J^e^KsZi~uo0pfG-|_$;u@uS<>Ci=t@5yu*lLeh?RZPunL{k>F@-ZjZMdM540Y2w}3+c@qAnEn=^aQkMj173|PXc&QgtrP2+yA^% zS~|6S!NKunr3~`Hw{NB)DU`pHf|MO2llpqxZdNS>c|5Jjx$53$6b_&RAi{y-Xl-eU zN=}~NJ?zg=WTU74t+;=V0K)M!P{{!{`!R_fvabvUx-3{wfa@I9$^|Gq6KI!{SYNQp zLEXsmVBQ9Y2^1Pe*@bmMVRy@i)09y5-9ge;A zbQPz$52Npdxo`FtWi&)UCxaL&^HyLXjlnpLU}Gi>Sg*z48;JlT0_kZEh+-gcg43LP zTn}m?Dl0dD%8<~X!d4fB2H-i%M3o$%Y!dgLrdtWX@u=PV;rC`M7x>#fY~5pCCQ46} zji^s|7iA=1(#_Ii)D6W;MQ62q)D|6e3=B^2Hk2oOKj*+^Zs0!9-`%|rP=k$`Sz6#@ zef_@Q^5&h_jO3Y?>?j~;mRsZGJxTfGFi>zoLqh{QUS6ZSj@;Qy23^8{J+x4|ZqNmUa*{_a2HZC#e{_BQ9-yDokEy0; zeogHUrIyh3ZjES7T)~GZ916i`DT4o~_(xsNK3_BW3##7ib7R`d#6`R`fKhBpP4mFV zd248_V2_}kn5>Sou*&KjlAwE_TC>us*x%dR=w5Njxc>k@BPsFG*|~S9I^bISO7ahk z%J@HmV%a{Y>R(qTotb1yOy-YM{PKBDx?t`t>w0Z3m*UW#H!p`6@tZV1zWZ|dh@B_^dUWPAzdj{))TL~#u@p=cu`32{ z%QWX0>sdyjG^aj%_+VD!cEQB{ISta)$+G8jv*E&M)Jo;n!hYx$t#k;ZiA#{*``|v_ zD#m3*YjIb%ugrK)Xj}TLn7?Uf@4Pe~be8Ye&r7n3UrA@(V}|`@PUF|*<~TYm8Anfb z-B$h$@!8CPRE5-UpA6OVpC+IG$&jArw;D;u(SXA(VMVt1Iq8E{vLbJ^=Pf&1#(3Am zb97HJ;aucu2Q&lOK{Kfg8@O=nGI<_OvhcC1=QPh?M+ zv~%+38lD7(87&Rmdlq$>_&xQ4=1!>`ISrM0Tt=RbOQ>iJ&nqedhB@qJ)hP6BTI{R6 zL%F)0>e`Syb6u>cg3w4@pzE5RV6^s=`H?=$rca6YdYxq}#3jrgWn_QN2K^o`s#mYJ zHnE)Zy2(8cS8$jPw~&hDo zf-}ozhIU?B?`p6$yCn^Ud#G$x_%@18Lf2#QLi>$}BeFKPYWxF}FW!NU^tUv82`s~B zVI3DXPe0#k@U9SKu%n{-8FdH8IEk9f=mpO`b@js)TA0Ub;Rqxj?KV|6MxXl=&y#>6 zXz`&%#EzDi>-=XPoe=1_tEC{2NUx=Xcd4WqgRF+hvGo{;y-;!vCgi6x4DRpL{SLv$ zhYs~14kmL{$OW)-ENon{;T{`Z}O1ZrU)q zpLP^bT2hy%q7oMcroh8t_2rh~fwk+8RZN-o`YZF(CE|I6kA{;yvkC=I4KK-_Gh#xaYe6%7CNsmqXp$n3b)WTQ>ER zXwPmR=TVBiwf(>#W4PBLsiFlJzJn@(d{z{*Tq`dZ4;r|_L`CKInou|-B3^1HE~Nd% zBR&PXh>XK1CMAEB*-1wJLbF|b4jUqS#lVsn4)+!%BoGwHCkXxP7NAo7w+83>x&{17?cI?sM00Cw8 z+KbXWvC~FFFUK{Iz>5i9!_2@Ly#LcM`~N(gFgaR&<{cbD9)p8YX>003$~A0X{12qd BdGP=M literal 0 HcmV?d00001 diff --git a/examples/data-solutions/cloudsql-multiregion/main.tf b/examples/data-solutions/cloudsql-multiregion/main.tf index 2c0e81d1..9df7303a 100644 --- a/examples/data-solutions/cloudsql-multiregion/main.tf +++ b/examples/data-solutions/cloudsql-multiregion/main.tf @@ -40,9 +40,9 @@ module "db" { source = "../../../modules/cloudsql-instance" project_id = module.project.project_id network = module.vpc.self_link - name = "db" + name = "${var.prefix}-db" region = var.regions.primary - database_version = "POSTGRES_13" + database_version = var.database_version tier = var.tier replicas = { diff --git a/examples/data-solutions/cloudsql-multiregion/terraform.tfvars.sample b/examples/data-solutions/cloudsql-multiregion/terraform.tfvars.sample new file mode 100644 index 00000000..726e64e4 --- /dev/null +++ b/examples/data-solutions/cloudsql-multiregion/terraform.tfvars.sample @@ -0,0 +1,2 @@ +project_id = "datalake-001" +prefix = "prefix" diff --git a/examples/data-solutions/cloudsql-multiregion/variables.tf b/examples/data-solutions/cloudsql-multiregion/variables.tf index b49d91f0..f4a4a203 100644 --- a/examples/data-solutions/cloudsql-multiregion/variables.tf +++ b/examples/data-solutions/cloudsql-multiregion/variables.tf @@ -20,6 +20,12 @@ variable "cloudsql_psa_range" { default = "10.60.0.0/16" } +variable "database_version" { + description = "Database type and version to create." + type = string + default = "POSTGRES_13" +} + variable "prefix" { description = "Unique prefix used for resource names. Not used for project if 'project_create' is null." type = string @@ -46,6 +52,10 @@ variable "regions" { condition = contains(keys(var.regions), "primary") error_message = "Regions map must contain `primary` as a key." } + default = { + primary = "europe-west1" + replica = "europe-west3" + } } variable "tier" { From af8c078e881098e55b74193b29802891a8d9e2bd Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Tue, 12 Apr 2022 23:42:25 +0200 Subject: [PATCH 05/28] Updates to README --- .../cloudsql-multiregion/README.md | 46 +++++++++---------- .../terraform.tfvars.sample | 4 +- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/examples/data-solutions/cloudsql-multiregion/README.md b/examples/data-solutions/cloudsql-multiregion/README.md index 9e5b280b..b55f75f4 100644 --- a/examples/data-solutions/cloudsql-multiregion/README.md +++ b/examples/data-solutions/cloudsql-multiregion/README.md @@ -1,49 +1,45 @@ # Cloud SQL instance with multi-region read replicas -This example creates the [Cloud SQL instance](https://cloud.google.com/sql) with multi-reagion read replica solution described in the [`Cloud SQL for PostgreSQL disaster recovery`](https://cloud.google.com/architecture/cloud-sql-postgres-disaster-recovery-complete-failover-fallback) article. +This example creates a [Cloud SQL instance](https://cloud.google.com/sql) with multi-region read replicas as described in the [Cloud SQL for PostgreSQL disaster recovery](https://cloud.google.com/architecture/cloud-sql-postgres-disaster-recovery-complete-failover-fallback) article. -The solution is resiliant to a regional outage. To get familiar with the procedure needed in the unfortunate case of a disaster recovery, we suggest to follow steps described in the [`Simulating a disaster (region outage)`](https://cloud.google.com/architecture/cloud-sql-postgres-disaster-recovery-complete-failover-fallback#phase-2) article. +The solution is resilient to a regional outage. To get familiar with the procedure needed in the unfortunate case of a disaster recovery, please follow steps described in [part two](https://cloud.google.com/architecture/cloud-sql-postgres-disaster-recovery-complete-failover-fallback#phase-2) of the aforementioned article. The solution will use: -- Postgre SQL instance with Private IP +- A VPC with Private Service Access to deploy the instances +- Postgre SQL instanced with Private IP This is the high level diagram: ![Cloud SQL multi-region.](diagram.png "Cloud SQL multi-region") -## Move to real use case consideration -In the example we implemented some compromise to keep the example minimal and easy to read. On a real word use case, you may evaluate the option to: - - Configure a Shared-VPC - - Use VPC-SC to mitigate data exfiltration +# Requirements -## Deploy your enviroment +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. -We assume the identiy running the following steps has the following role: - - `resourcemanager.projectCreator` in case a new project will be created. - - `owner` on the project in case you use an existing project. +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 + +Configure the Terraform variables in your `terraform.tfvars` file. You need to specify at least the `project_id` and `prefix` variables. See [`terraform.tfvars.sample`](terraform.tfvars.sample) as starting point. Run Terraform init: ``` $ terraform init -``` - -Configure the Terraform variable in your `terraform.tfvars` file. You need to spefify at least the following variables: - -``` -data_eng_principals = ["user:data-eng@domain.com"] -project_id = "datalake-001" -prefix = "prefix" -``` - -You can run now: - -``` $ terraform apply ``` -You should see the output of the Terraform script with resources created and some command pre-created for you to run the example following steps below. +You should see the output of the Terraform script with resources created and some commands that you'll need in the following steps below. + TBC + +## Move to real use case consideration + +This implementation is intentionally minimal and easy to read. A real world use case should consider: + - Using a Shared VPC + - Using VPC-SC to mitigate data exfiltration + ## Variables diff --git a/examples/data-solutions/cloudsql-multiregion/terraform.tfvars.sample b/examples/data-solutions/cloudsql-multiregion/terraform.tfvars.sample index 726e64e4..c23fe33d 100644 --- a/examples/data-solutions/cloudsql-multiregion/terraform.tfvars.sample +++ b/examples/data-solutions/cloudsql-multiregion/terraform.tfvars.sample @@ -1,2 +1,2 @@ -project_id = "datalake-001" -prefix = "prefix" +project_id = "datalake-001" +prefix = "prefix" From 695e855bd7d88e54cf1ace0a33a853eb36cf6b82 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Tue, 12 Apr 2022 23:43:38 +0200 Subject: [PATCH 06/28] Update vars and outputs --- examples/data-solutions/cloudsql-multiregion/README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/data-solutions/cloudsql-multiregion/README.md b/examples/data-solutions/cloudsql-multiregion/README.md index b55f75f4..7a70ee98 100644 --- a/examples/data-solutions/cloudsql-multiregion/README.md +++ b/examples/data-solutions/cloudsql-multiregion/README.md @@ -39,7 +39,6 @@ TBC This implementation is intentionally minimal and easy to read. A real world use case should consider: - Using a Shared VPC - Using VPC-SC to mitigate data exfiltration - ## Variables @@ -48,11 +47,11 @@ This implementation is intentionally minimal and easy to read. A real world use |---|---|:---:|:---:|:---:| | [prefix](variables.tf#L29) | Unique prefix used for resource names. Not used for project if 'project_create' is null. | string | ✓ | | | [project_id](variables.tf#L43) | Project id, references existing project if `project_create` is null. | string | ✓ | | -| [regions](variables.tf#L48) | Map of instance_name => location where instances will be deployed. | map(string) | ✓ | | | [cloudsql_psa_range](variables.tf#L17) | Range used for the Private Service Access. | string | | "10.60.0.0/16" | | [database_version](variables.tf#L23) | Database type and version to create. | string | | "POSTGRES_13" | | [project_create](variables.tf#L34) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | | null | -| [tier](variables.tf#L57) | The machine type to use for the instances. See See https://cloud.google.com/sql/docs/postgres/create-instance#machine-types. | string | | "db-g1-small" | +| [regions](variables.tf#L48) | Map of instance_name => location where instances will be deployed. | map(string) | | {…} | +| [tier](variables.tf#L61) | The machine type to use for the instances. See See https://cloud.google.com/sql/docs/postgres/create-instance#machine-types. | string | | "db-g1-small" | ## Outputs From 045806cfa4dbe278e45d8bfcb66ef89d7c1fa48e Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Wed, 13 Apr 2022 09:05:28 +0200 Subject: [PATCH 07/28] Improve project module README (#627) * improve module README * Fix a few typos Co-authored-by: Julio Castillo --- modules/project/README.md | 116 +++++++++++++++++++++++++++++++++++--- 1 file changed, 109 insertions(+), 7 deletions(-) diff --git a/modules/project/README.md b/modules/project/README.md index 42d435d9..035aee41 100644 --- a/modules/project/README.md +++ b/modules/project/README.md @@ -1,8 +1,19 @@ # Project Module -## Examples +This module implements the creation and management of one GCP project including IAM, organization policies, Shared VPC host or service attachment, service API activation, and tag attachment. It also offers a convenient way to refer to managed service identities (aka robot service accounts) for APIs. -### Minimal example with IAM +## IAM Examples + +IAM is controlled via several variables that implement different levels of control: + +- `group_iam` and `iam` configure authoritative bindings that manage individual roles exclusively, mapping to the [`google_project_iam_binding`](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_iam#google_project_iam_binding) resource +- `iam_additive` and `iam_additive_members` configure additive bindings that only manage individual role/member pairs, mapping to the [`google_project_iam_member`](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_iam#google_project_iam_member) resource + +Be mindful about the service identity roles when using authoritative IAM, as you might end up inadvertently removing a role from a [service identity](https://cloud.google.com/iam/docs/service-accounts#google-managed) or default service account. For example, using `roles/editor` with `iam` or `group_iam` will remove the default permissions for the Cloud Services identity. A simple workaround for these scenarios is described below. + +### Authoritative IAM + +The `iam` variable is based on role keys and is typically used for service accounts, or where member values can be dynamic and would create potential problems in the underlying `for_each` cycle. ```hcl locals { @@ -28,16 +39,43 @@ module "project" { # tftest modules=1 resources=4 ``` -### Minimal example with IAM additive roles +The `group_iam` variable uses group email addresses as keys and is a convenient way to assign roles to humans following Google's best practices. The end result is readable code that also serves as documentation. + +```hcl +module "project" { + source = "./modules/project" + billing_account = "123456-123456-123456" + name = "project-example" + parent = "folders/1234567890" + prefix = "foo" + services = [ + "container.googleapis.com", + "stackdriver.googleapis.com" + ] + group_iam = { + "gcp-security-admins@example.com" = [ + "roles/cloudasset.owner", + "roles/cloudsupport.techSupportEditor", + "roles/iam.securityReviewer", + "roles/logging.admin", + ] + } +} +# tftest modules=1 resources=7 +``` + +### Additive IAM + +Additive IAM is typically used where bindings for specific roles are controlled by different modules or in different Terraform stages. One example is when the project is created by one team but a different team manages service account creation for the project, and some of the project-level roles overlap in the two configurations. ```hcl module "project" { source = "./modules/project" name = "project-example" - iam_additive = { "roles/viewer" = [ - "group:one@example.org", "group:two@xample.org" + "group:one@example.org", + "group:two@xample.org" ], "roles/storage.objectAdmin" = [ "group:two@example.org" @@ -50,13 +88,54 @@ module "project" { # tftest modules=1 resources=5 ``` -### Shared VPC service +### Service Identities and authoritative IAM + +As mentioned above, there are cases where authoritative management of specific IAM roles results in removal of default bindings from service identities. One example is outlined below, with a simple workaround leveraging the `service_accounts` output to identify the service identity. A full list of service identities and their roles can be found [here](https://cloud.google.com/iam/docs/service-agents). ```hcl module "project" { source = "./modules/project" name = "project-example" + group_iam = { + "roles/editor" = [ + "group:foo@example.com" + ] + } + iam = { + "roles/editor" = [ + "serviceAccount:${module.project.service_accounts.cloud_services}" + ] + } +} +# tftest modules=1 resources=3 +``` +## Shared VPC service + +The module allows managing Shared VPC status for both hosts and service projects, and includes a simple way of assigning Shared VPC roles to service identities. + +### Host project + +You can enable Shared VPC Host at the project level and manage project service association independently. + +```hcl +module "project" { + source = "./modules/project" + name = "project-example" + shared_vpc_host_config = { + enabled = true + service_projects = [] + } +} +# tftest modules=1 resources=2 +``` + +### Service project + +```hcl +module "project" { + source = "./modules/project" + name = "project-example" shared_vpc_service_config = { attach = true host_project = "my-host-project" @@ -76,7 +155,7 @@ module "project" { # tftest modules=1 resources=6 ``` -### Organization policies +## Organization policies ```hcl module "project" { @@ -184,6 +263,8 @@ module "project-host" { ## Cloud KMS encryption keys +The module offers a simple, centralized way to assign `roles/cloudkms.cryptoKeyEncrypterDecrypter` to service identities. + ```hcl module "project" { source = "./modules/project" @@ -238,6 +319,27 @@ module "project" { # tftest modules=2 resources=6 ``` +## Outputs + +Most of this module's outputs depend on its resources, to allow Terraform to compute all dependencies required for the project to be correctly configured. This allows you to reference outputs like `project_id` in other modules or resources without having to worry about setting `depends_on` blocks manually. + +One non-obvious output is `service_accounts`, which offers simple way to discover service identities and default service accounts, and guarantees that service identities that require an API call to trigger creation (like GCS or BigQuery) exist before use. + +```hcl +module "project" { + source = "./modules/project" + name = "project-example" + services = [ + "compute.googleapis.com" + ] +} + +output "compute_robot" { + value = module.project.service_accounts.robots.compute +} +# tftest modules=1 resources=2 +``` + From 4944871ad070e6f2d1308666542237465fed91d1 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Wed, 13 Apr 2022 09:06:18 +0200 Subject: [PATCH 08/28] Update README.md --- modules/project/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/project/README.md b/modules/project/README.md index 035aee41..3e00fde3 100644 --- a/modules/project/README.md +++ b/modules/project/README.md @@ -4,7 +4,7 @@ This module implements the creation and management of one GCP project including ## IAM Examples -IAM is controlled via several variables that implement different levels of control: +IAM is managed via several variables that implement different levels of control: - `group_iam` and `iam` configure authoritative bindings that manage individual roles exclusively, mapping to the [`google_project_iam_binding`](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_iam#google_project_iam_binding) resource - `iam_additive` and `iam_additive_members` configure additive bindings that only manage individual role/member pairs, mapping to the [`google_project_iam_member`](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_iam#google_project_iam_member) resource From 19027e587e38b31e816a7731f6f1ceca60b93d67 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Wed, 13 Apr 2022 09:08:01 +0200 Subject: [PATCH 09/28] Update README.md --- modules/project/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/project/README.md b/modules/project/README.md index 3e00fde3..1f098cf6 100644 --- a/modules/project/README.md +++ b/modules/project/README.md @@ -9,7 +9,7 @@ IAM is managed via several variables that implement different levels of control: - `group_iam` and `iam` configure authoritative bindings that manage individual roles exclusively, mapping to the [`google_project_iam_binding`](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_iam#google_project_iam_binding) resource - `iam_additive` and `iam_additive_members` configure additive bindings that only manage individual role/member pairs, mapping to the [`google_project_iam_member`](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_iam#google_project_iam_member) resource -Be mindful about the service identity roles when using authoritative IAM, as you might end up inadvertently removing a role from a [service identity](https://cloud.google.com/iam/docs/service-accounts#google-managed) or default service account. For example, using `roles/editor` with `iam` or `group_iam` will remove the default permissions for the Cloud Services identity. A simple workaround for these scenarios is described below. +Be mindful about service identity roles when using authoritative IAM, as you might inadvertently remove a role from a [service identity](https://cloud.google.com/iam/docs/service-accounts#google-managed) or default service account. For example, using `roles/editor` with `iam` or `group_iam` will remove the default permissions for the Cloud Services identity. A simple workaround for these scenarios is described below. ### Authoritative IAM From 1cfb5dcaecbae046123897d1c9d8a29360846b87 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Wed, 13 Apr 2022 09:10:56 +0200 Subject: [PATCH 10/28] Update README.md --- modules/project/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/project/README.md b/modules/project/README.md index 1f098cf6..2d1abb08 100644 --- a/modules/project/README.md +++ b/modules/project/README.md @@ -323,7 +323,7 @@ module "project" { Most of this module's outputs depend on its resources, to allow Terraform to compute all dependencies required for the project to be correctly configured. This allows you to reference outputs like `project_id` in other modules or resources without having to worry about setting `depends_on` blocks manually. -One non-obvious output is `service_accounts`, which offers simple way to discover service identities and default service accounts, and guarantees that service identities that require an API call to trigger creation (like GCS or BigQuery) exist before use. +One non-obvious output is `service_accounts`, which offers a simple way to discover service identities and default service accounts, and guarantees that service identities that require an API call to trigger creation (like GCS or BigQuery) exist before use. ```hcl module "project" { From a3f03ac213af32824885c89092e1afbefc78a1fc Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Tue, 12 Apr 2022 19:01:34 +0200 Subject: [PATCH 11/28] Add KMS on CloudSQL module --- modules/cloudsql-instance/README.md | 53 ++++++++++++++++++++++++++ modules/cloudsql-instance/main.tf | 16 +++++--- modules/cloudsql-instance/outputs.tf | 13 +++++++ modules/cloudsql-instance/variables.tf | 15 ++++++-- modules/project/service-accounts.tf | 6 ++- 5 files changed, 92 insertions(+), 11 deletions(-) diff --git a/modules/cloudsql-instance/README.md b/modules/cloudsql-instance/README.md index 213398d7..d5c050c8 100644 --- a/modules/cloudsql-instance/README.md +++ b/modules/cloudsql-instance/README.md @@ -93,6 +93,59 @@ module "db" { } # tftest modules=1 resources=6 ``` + +### CMEK encryption pippo +```hcl + +module "project" { + source = "./modules/project" + billing_account = var.billing_account_id + parent = var.organization_id + name = "my-db-project" + services = [ + "servicenetworking.googleapis.com" + ] +} + +resource "google_project_service_identity" "jit_si" { + provider = google-beta + project = module.project.project_id + service = "sqladmin.googleapis.com" +} + +module "kms" { + source = "./modules/kms" + project_id = module.project.project_id + keyring = { + name = "keyring" + location = var.region + } + keys = { + key-sql = null + } + key_iam = { + key-sql = { + "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [ + "serviceAccount:${google_project_service_identity.jit_si.email}" + ] + } + } +} + +module "db" { + source = "./modules/cloudsql-instance" + project_id = module.project.project_id + encryption_key_name = module.kms.keys["key-sql"].id + network = var.vpc.self_link + name = "db" + region = var.region + database_version = "POSTGRES_13" + tier = "db-g1-small" +} + +# tftest modules=3 resources=8 +``` + ## Variables diff --git a/modules/cloudsql-instance/main.tf b/modules/cloudsql-instance/main.tf index 0f817b29..dcf92fe1 100644 --- a/modules/cloudsql-instance/main.tf +++ b/modules/cloudsql-instance/main.tf @@ -43,10 +43,12 @@ locals { } resource "google_sql_database_instance" "primary" { - project = var.project_id - name = "${local.prefix}${var.name}" - region = var.region - database_version = var.database_version + provider = google-beta + project = var.project_id + name = "${local.prefix}${var.name}" + region = var.region + database_version = var.database_version + encryption_key_name = var.encryption_key_name settings { tier = var.tier @@ -104,11 +106,13 @@ resource "google_sql_database_instance" "primary" { } resource "google_sql_database_instance" "replicas" { - for_each = local.has_replicas ? var.replicas : {} + provider = google-beta + for_each = length(var.replicas) > 0 ? var.replicas : {} project = var.project_id name = "${local.prefix}${each.key}" - region = each.value + region = each.value.region database_version = var.database_version + encryption_key_name = each.value.encryption_key_name master_instance_name = google_sql_database_instance.primary.name settings { diff --git a/modules/cloudsql-instance/outputs.tf b/modules/cloudsql-instance/outputs.tf index e2ca316d..38bfc951 100644 --- a/modules/cloudsql-instance/outputs.tf +++ b/modules/cloudsql-instance/outputs.tf @@ -66,6 +66,19 @@ output "ips" { } } +output "name" { + description = "Name of the primary instance." + value = google_sql_database_instance.primary.name +} + +output "names" { + description = "Names of all instances." + value = { + for id, instance in local._all_intances : + id => instance.name + } +} + output "self_link" { description = "Self link of the primary instance." value = google_sql_database_instance.primary.self_link diff --git a/modules/cloudsql-instance/variables.tf b/modules/cloudsql-instance/variables.tf index e59736ba..00130045 100644 --- a/modules/cloudsql-instance/variables.tf +++ b/modules/cloudsql-instance/variables.tf @@ -76,6 +76,12 @@ variable "disk_type" { default = "PD_SSD" } +variable "encryption_key_name" { + description = "The full path to the encryption key used for the CMEK disk encryption." + type = string + default = null +} + variable "flags" { description = "Map FLAG_NAME=>VALUE for database-specific tuning." type = map(string) @@ -115,9 +121,12 @@ variable "region" { } variable "replicas" { - description = "Map of NAME=>REGION for additional read replicas. Set to null to disable replica creation." - type = map(any) - default = null + description = "Map of NAME=> {REGION, KMS_KEY} for additional read replicas. Set to null to disable replica creation." + type = map(object({ + region = string + encryption_key_name = string + })) + default = {} } variable "tier" { diff --git a/modules/project/service-accounts.tf b/modules/project/service-accounts.tf index eae98e23..f5cffed4 100644 --- a/modules/project/service-accounts.tf +++ b/modules/project/service-accounts.tf @@ -42,6 +42,7 @@ locals { gcf = "service-%s@gcf-admin-robot" pubsub = "service-%s@gcp-sa-pubsub" secretmanager = "service-%s@gcp-sa-secretmanager" + sql = "service-%s@gcp-sa-cloud-sql" storage = "service-%s@gs-project-accounts" } service_accounts_default = { @@ -56,9 +57,10 @@ locals { k => "${format(v, local.project.number)}.iam.gserviceaccount.com" } service_accounts_jit_services = [ - "secretmanager.googleapis.com", + "cloudasset.googleapis.com", "pubsub.googleapis.com", - "cloudasset.googleapis.com" + "secretmanager.googleapis.com", + "sqladmin.googleapis.com" ] service_accounts_cmek_service_keys = distinct(flatten([ for s in keys(var.service_encryption_key_ids) : [ From 952e18d0f1b97da696a34cab993daa8b5da3b70c Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Wed, 13 Apr 2022 00:22:54 +0200 Subject: [PATCH 12/28] Add sqladmin to project jit_si and fix some documentation --- modules/cloudsql-instance/README.md | 47 +++++++++---------- modules/cloudsql-instance/main.tf | 2 +- modules/cloudsql-instance/variables.tf | 6 +-- modules/project/service-accounts.tf | 1 + .../cloudsql_instance/fixture/variables.tf | 2 +- tests/modules/cloudsql_instance/test_plan.py | 13 +++-- 6 files changed, 34 insertions(+), 37 deletions(-) diff --git a/modules/cloudsql-instance/README.md b/modules/cloudsql-instance/README.md index d5c050c8..75a7af0b 100644 --- a/modules/cloudsql-instance/README.md +++ b/modules/cloudsql-instance/README.md @@ -56,8 +56,8 @@ module "db" { tier = "db-g1-small" replicas = { - replica1 = "europe-west3" - replica2 = "us-central1" + replica1 = { region = "europe-west3", encryption_key_name = null } + replica2 = { region = "us-central1", encryption_key_name = null } } } # tftest modules=1 resources=3 @@ -103,16 +103,11 @@ module "project" { parent = var.organization_id name = "my-db-project" services = [ - "servicenetworking.googleapis.com" + "servicenetworking.googleapis.com", + "sqladmin.googleapis.com", ] } -resource "google_project_service_identity" "jit_si" { - provider = google-beta - project = module.project.project_id - service = "sqladmin.googleapis.com" -} - module "kms" { source = "./modules/kms" project_id = module.project.project_id @@ -126,7 +121,7 @@ module "kms" { key_iam = { key-sql = { "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [ - "serviceAccount:${google_project_service_identity.jit_si.email}" + "serviceAccount:${module.project.service_accounts.robots.sqladmin}" ] } } @@ -143,9 +138,8 @@ module "db" { tier = "db-g1-small" } -# tftest modules=3 resources=8 +# tftest modules=3 resources=10 ``` - ## Variables @@ -153,11 +147,11 @@ module "db" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| | [database_version](variables.tf#L50) | Database type and version to create. | string | ✓ | | -| [name](variables.tf#L91) | Name of primary replica. | string | ✓ | | -| [network](variables.tf#L96) | VPC self link where the instances will be deployed. Private Service Networking must be enabled and configured in this VPC. | string | ✓ | | -| [project_id](variables.tf#L107) | The ID of the project where this instances will be created. | string | ✓ | | -| [region](variables.tf#L112) | Region of the primary replica. | string | ✓ | | -| [tier](variables.tf#L123) | The machine type to use for the instances. | string | ✓ | | +| [name](variables.tf#L97) | Name of primary instance. | string | ✓ | | +| [network](variables.tf#L102) | VPC self link where the instances will be deployed. Private Service Networking must be enabled and configured in this VPC. | string | ✓ | | +| [project_id](variables.tf#L113) | The ID of the project where this instances will be created. | string | ✓ | | +| [region](variables.tf#L118) | Region of the primary instance. | string | ✓ | | +| [tier](variables.tf#L132) | The machine type to use for the instances. | string | ✓ | | | [authorized_networks](variables.tf#L17) | Map of NAME=>CIDR_RANGE to allow to connect to the database(s). | map(string) | | null | | [availability_type](variables.tf#L23) | Availability type for the primary replica. Either `ZONAL` or `REGIONAL`. | string | | "ZONAL" | | [backup_configuration](variables.tf#L29) | Backup settings for primary instance. Will be automatically enabled if using MySQL with one or more replicas. | object({…}) | | {…} | @@ -165,11 +159,12 @@ module "db" { | [deletion_protection](variables.tf#L61) | Allow terraform to delete instances. | bool | | false | | [disk_size](variables.tf#L67) | Disk size in GB. Set to null to enable autoresize. | number | | null | | [disk_type](variables.tf#L73) | The type of data disk: `PD_SSD` or `PD_HDD`. | string | | "PD_SSD" | -| [flags](variables.tf#L79) | Map FLAG_NAME=>VALUE for database-specific tuning. | map(string) | | null | -| [labels](variables.tf#L85) | Labels to be attached to all instances. | map(string) | | null | -| [prefix](variables.tf#L101) | Prefix used to generate instance names. | string | | null | -| [replicas](variables.tf#L117) | Map of NAME=>REGION for additional read replicas. Set to null to disable replica creation. | map(any) | | null | -| [users](variables.tf#L128) | Map of users to create in the primary instance (and replicated to other replicas) in the format USER=>PASSWORD. For MySQL, anything afterr the first `@` (if persent) will be used as the user's host. Set PASSWORD to null if you want to get an autogenerated password. | map(string) | | null | +| [encryption_key_name](variables.tf#L79) | The full path to the encryption key used for the CMEK disk encryption of the primary instance. | string | | null | +| [flags](variables.tf#L85) | Map FLAG_NAME=>VALUE for database-specific tuning. | map(string) | | null | +| [labels](variables.tf#L91) | Labels to be attached to all instances. | map(string) | | null | +| [prefix](variables.tf#L107) | Prefix used to generate instance names. | string | | null | +| [replicas](variables.tf#L123) | Map of NAME=> {REGION, KMS_KEY} for additional read replicas. Set to null to disable replica creation. | map(object({…})) | | {} | +| [users](variables.tf#L137) | Map of users to create in the primary instance (and replicated to other replicas) in the format USER=>PASSWORD. For MySQL, anything afterr the first `@` (if persent) will be used as the user's host. Set PASSWORD to null if you want to get an autogenerated password. | map(string) | | null | ## Outputs @@ -182,8 +177,10 @@ module "db" { | [instances](outputs.tf#L50) | Cloud SQL instance resources. | ✓ | | [ip](outputs.tf#L56) | IP address of the primary instance. | | | [ips](outputs.tf#L61) | IP addresses of all instances. | | -| [self_link](outputs.tf#L69) | Self link of the primary instance. | | -| [self_links](outputs.tf#L74) | Self links of all instances. | | -| [user_passwords](outputs.tf#L82) | Map of containing the password of all users created through terraform. | ✓ | +| [name](outputs.tf#L69) | Name of the primary instance. | | +| [names](outputs.tf#L74) | Names of all instances. | | +| [self_link](outputs.tf#L82) | Self link of the primary instance. | | +| [self_links](outputs.tf#L87) | Self links of all instances. | | +| [user_passwords](outputs.tf#L95) | Map of containing the password of all users created through terraform. | ✓ | diff --git a/modules/cloudsql-instance/main.tf b/modules/cloudsql-instance/main.tf index dcf92fe1..c76f53d4 100644 --- a/modules/cloudsql-instance/main.tf +++ b/modules/cloudsql-instance/main.tf @@ -107,7 +107,7 @@ resource "google_sql_database_instance" "primary" { resource "google_sql_database_instance" "replicas" { provider = google-beta - for_each = length(var.replicas) > 0 ? var.replicas : {} + for_each = local.has_replicas ? var.replicas : {} project = var.project_id name = "${local.prefix}${each.key}" region = each.value.region diff --git a/modules/cloudsql-instance/variables.tf b/modules/cloudsql-instance/variables.tf index 00130045..30acd915 100644 --- a/modules/cloudsql-instance/variables.tf +++ b/modules/cloudsql-instance/variables.tf @@ -77,7 +77,7 @@ variable "disk_type" { } variable "encryption_key_name" { - description = "The full path to the encryption key used for the CMEK disk encryption." + description = "The full path to the encryption key used for the CMEK disk encryption of the primary instance." type = string default = null } @@ -95,7 +95,7 @@ variable "labels" { } variable "name" { - description = "Name of primary replica." + description = "Name of primary instance." type = string } @@ -116,7 +116,7 @@ variable "project_id" { } variable "region" { - description = "Region of the primary replica." + description = "Region of the primary instance." type = string } diff --git a/modules/project/service-accounts.tf b/modules/project/service-accounts.tf index f5cffed4..5ba40fc4 100644 --- a/modules/project/service-accounts.tf +++ b/modules/project/service-accounts.tf @@ -44,6 +44,7 @@ locals { secretmanager = "service-%s@gcp-sa-secretmanager" sql = "service-%s@gcp-sa-cloud-sql" storage = "service-%s@gs-project-accounts" + sqladmin = "service-%s@gcp-sa-cloud-sql" } service_accounts_default = { compute = "${local.project.number}-compute@developer.gserviceaccount.com" diff --git a/tests/modules/cloudsql_instance/fixture/variables.tf b/tests/modules/cloudsql_instance/fixture/variables.tf index 62ff27a3..d6cc7d83 100644 --- a/tests/modules/cloudsql_instance/fixture/variables.tf +++ b/tests/modules/cloudsql_instance/fixture/variables.tf @@ -94,7 +94,7 @@ variable "region" { } variable "replicas" { - type = map(any) + type = any default = null } diff --git a/tests/modules/cloudsql_instance/test_plan.py b/tests/modules/cloudsql_instance/test_plan.py index 3c45f5c6..c4e8ba0a 100644 --- a/tests/modules/cloudsql_instance/test_plan.py +++ b/tests/modules/cloudsql_instance/test_plan.py @@ -35,8 +35,8 @@ def test_prefix(plan_runner): assert r['values']['name'] == 'prefix-db' replicas = """{ - replica1 = "europe-west3" - replica2 = "us-central1" + replica1 = { region = "europe-west3", encryption_key_name = null } + replica2 = { region = "us-central1", encryption_key_name = null } }""" _, resources = plan_runner(prefix="prefix") @@ -49,8 +49,8 @@ def test_replicas(plan_runner): "Test replicated instance." replicas = """{ - replica1 = "europe-west3" - replica2 = "us-central1" + replica1 = { region = "europe-west3", encryption_key_name = null } + replica2 = { region = "us-central1", encryption_key_name = null } }""" _, resources = plan_runner(replicas=replicas, prefix="prefix") @@ -80,10 +80,9 @@ def test_mysql_replicas_enables_backup(plan_runner): "Test MySQL backup setup with replicas." replicas = """{ - replica1 = "europe-west3" + replica1 = { region = "europe-west3", encryption_key_name = null } }""" - _, resources = plan_runner(replicas=replicas, - database_version="MYSQL_8_0") + _, resources = plan_runner(replicas=replicas, database_version="MYSQL_8_0") assert len(resources) == 2 primary = [r for r in resources if r['name'] == 'primary'][0] backup_config = primary['values']['settings'][0]['backup_configuration'][0] From 24930ce397d648a19ae5ab39ba60b799f4fd68e9 Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Wed, 13 Apr 2022 08:59:14 +0200 Subject: [PATCH 13/28] Fix README, bye bye pippo :-) --- modules/cloudsql-instance/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cloudsql-instance/README.md b/modules/cloudsql-instance/README.md index 75a7af0b..73b2e3f2 100644 --- a/modules/cloudsql-instance/README.md +++ b/modules/cloudsql-instance/README.md @@ -94,7 +94,7 @@ module "db" { # tftest modules=1 resources=6 ``` -### CMEK encryption pippo +### CMEK encryption ```hcl module "project" { From d8676f09b4a05481f6edcf6b5f106f8828fe0091 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Wed, 13 Apr 2022 10:22:33 +0200 Subject: [PATCH 14/28] FAST: allow changing tag names from variables in resman (#628) --- fast/stages/01-resman/README.md | 21 ++++++++++--------- fast/stages/01-resman/branch-data-platform.tf | 12 ++++++++--- fast/stages/01-resman/branch-networking.tf | 12 ++++++++--- fast/stages/01-resman/branch-sandbox.tf | 4 +++- fast/stages/01-resman/branch-security.tf | 4 +++- fast/stages/01-resman/branch-teams.tf | 12 ++++++++--- fast/stages/01-resman/organization.tf | 8 +++---- fast/stages/01-resman/outputs.tf | 1 + fast/stages/01-resman/variables.tf | 17 +++++++++++++++ 9 files changed, 66 insertions(+), 25 deletions(-) diff --git a/fast/stages/01-resman/README.md b/fast/stages/01-resman/README.md index 30a86830..e3f07c67 100644 --- a/fast/stages/01-resman/README.md +++ b/fast/stages/01-resman/README.md @@ -183,20 +183,21 @@ Due to its simplicity, this stage lends itself easily to customizations: adding | [groups](variables.tf#L118) | Group names to grant organization-level permissions. | map(string) | | {…} | 00-bootstrap | | [organization_policy_configs](variables.tf#L143) | Organization policies customization. | object({…}) | | null | | | [outputs_location](variables.tf#L151) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable | string | | null | | -| [team_folders](variables.tf#L168) | Team folders to be created. Format is described in a code comment. | map(object({…})) | | null | | +| [tag_names](variables.tf#L168) | Customized names for resource management tags. | object({…}) | | {…} | | +| [team_folders](variables.tf#L185) | Team folders to be created. Format is described in a code comment. | map(object({…})) | | null | | ## Outputs | name | description | sensitive | consumers | |---|---|:---:|---| -| [cicd_repositories](outputs.tf#L156) | WIF configuration for CI/CD repositories. | | | -| [dataplatform](outputs.tf#L168) | Data for the Data Platform stage. | | | -| [networking](outputs.tf#L184) | Data for the networking stage. | | | -| [project_factories](outputs.tf#L193) | Data for the project factories stage. | | | -| [providers](outputs.tf#L209) | Terraform provider files for this stage and dependent stages. | ✓ | 02-networking · 02-security · 03-dataplatform · xx-sandbox · xx-teams | -| [sandbox](outputs.tf#L216) | Data for the sandbox stage. | | xx-sandbox | -| [security](outputs.tf#L226) | Data for the networking stage. | | 02-security | -| [teams](outputs.tf#L236) | Data for the teams stage. | | | -| [tfvars](outputs.tf#L249) | Terraform variable files for the following stages. | ✓ | | +| [cicd_repositories](outputs.tf#L157) | WIF configuration for CI/CD repositories. | | | +| [dataplatform](outputs.tf#L169) | Data for the Data Platform stage. | | | +| [networking](outputs.tf#L185) | Data for the networking stage. | | | +| [project_factories](outputs.tf#L194) | Data for the project factories stage. | | | +| [providers](outputs.tf#L210) | Terraform provider files for this stage and dependent stages. | ✓ | 02-networking · 02-security · 03-dataplatform · xx-sandbox · xx-teams | +| [sandbox](outputs.tf#L217) | Data for the sandbox stage. | | xx-sandbox | +| [security](outputs.tf#L227) | Data for the networking stage. | | 02-security | +| [teams](outputs.tf#L237) | Data for the teams stage. | | | +| [tfvars](outputs.tf#L250) | Terraform variable files for the following stages. | ✓ | | diff --git a/fast/stages/01-resman/branch-data-platform.tf b/fast/stages/01-resman/branch-data-platform.tf index d518c9c1..c5e186ae 100644 --- a/fast/stages/01-resman/branch-data-platform.tf +++ b/fast/stages/01-resman/branch-data-platform.tf @@ -21,7 +21,9 @@ module "branch-dp-folder" { parent = "organizations/${var.organization.id}" name = "Data Platform" tag_bindings = { - context = try(module.organization.tag_values["context/data"].id, null) + context = try( + module.organization.tag_values["${var.tag_names.context}/data"].id, null + ) } } @@ -39,7 +41,9 @@ module "branch-dp-dev-folder" { "roles/resourcemanager.projectCreator" = [module.branch-dp-dev-sa.iam_email] } tag_bindings = { - context = try(module.organization.tag_values["environment/development"].id, null) + context = try( + module.organization.tag_values["${var.tag_names.environment}/development"].id, null + ) } } @@ -57,7 +61,9 @@ module "branch-dp-prod-folder" { "roles/resourcemanager.projectCreator" = [module.branch-dp-prod-sa.iam_email] } tag_bindings = { - context = try(module.organization.tag_values["environment/production"].id, null) + context = try( + module.organization.tag_values["${var.tag_names.environment}/production"].id, null + ) } } diff --git a/fast/stages/01-resman/branch-networking.tf b/fast/stages/01-resman/branch-networking.tf index 3d85f1be..5cf3c6e0 100644 --- a/fast/stages/01-resman/branch-networking.tf +++ b/fast/stages/01-resman/branch-networking.tf @@ -39,7 +39,9 @@ module "branch-network-folder" { "roles/compute.xpnAdmin" = [module.branch-network-sa.iam_email] } tag_bindings = { - context = try(module.organization.tag_values["context/networking"].id, null) + context = try( + module.organization.tag_values["${var.tag_names.context}/networking"].id, null + ) } } @@ -54,7 +56,9 @@ module "branch-network-prod-folder" { ] } tag_bindings = { - environment = try(module.organization.tag_values["environment/production"].id, null) + environment = try( + module.organization.tag_values["${var.tag_names.environment}/production"].id, null + ) } } @@ -69,7 +73,9 @@ module "branch-network-dev-folder" { ] } tag_bindings = { - environment = try(module.organization.tag_values["environment/development"].id, null) + environment = try( + module.organization.tag_values["${var.tag_names.environment}/development"].id, null + ) } } diff --git a/fast/stages/01-resman/branch-sandbox.tf b/fast/stages/01-resman/branch-sandbox.tf index dda4b1fc..f2ba0bfb 100644 --- a/fast/stages/01-resman/branch-sandbox.tf +++ b/fast/stages/01-resman/branch-sandbox.tf @@ -38,7 +38,9 @@ module "branch-sandbox-folder" { } } tag_bindings = { - context = try(module.organization.tag_values["context/sandbox"].id, null) + context = try( + module.organization.tag_values["${var.tag_names.context}/sandbox"].id, null + ) } } diff --git a/fast/stages/01-resman/branch-security.tf b/fast/stages/01-resman/branch-security.tf index bba54b6c..c2067304 100644 --- a/fast/stages/01-resman/branch-security.tf +++ b/fast/stages/01-resman/branch-security.tf @@ -40,7 +40,9 @@ module "branch-security-folder" { "roles/resourcemanager.projectCreator" = [module.branch-security-sa.iam_email] } tag_bindings = { - context = try(module.organization.tag_values["context/security"].id, null) + context = try( + module.organization.tag_values["${var.tag_names.context}/security"].id, null + ) } } diff --git a/fast/stages/01-resman/branch-teams.tf b/fast/stages/01-resman/branch-teams.tf index a5a16c76..124301d5 100644 --- a/fast/stages/01-resman/branch-teams.tf +++ b/fast/stages/01-resman/branch-teams.tf @@ -21,7 +21,9 @@ module "branch-teams-folder" { parent = "organizations/${var.organization.id}" name = "Teams" tag_bindings = { - context = try(module.organization.tag_values["context/teams"].id, null) + context = try( + module.organization.tag_values["${var.tag_names.context}/teams"].id, null + ) } } @@ -90,7 +92,9 @@ module "branch-teams-team-dev-folder" { "roles/resourcemanager.projectCreator" = [module.branch-teams-dev-pf-sa.iam_email] } tag_bindings = { - environment = try(module.organization.tag_values["environment/development"].id, null) + environment = try( + module.organization.tag_values["${var.tag_names.environment}/development"].id, null + ) } } @@ -111,7 +115,9 @@ module "branch-teams-team-prod-folder" { "roles/resourcemanager.projectCreator" = [module.branch-teams-prod-pf-sa.iam_email] } tag_bindings = { - environment = try(module.organization.tag_values["environment/production"].id, null) + environment = try( + module.organization.tag_values["${var.tag_names.environment}/production"].id, null + ) } } diff --git a/fast/stages/01-resman/organization.tf b/fast/stages/01-resman/organization.tf index 4f462059..b917b514 100644 --- a/fast/stages/01-resman/organization.tf +++ b/fast/stages/01-resman/organization.tf @@ -151,7 +151,7 @@ module "organization" { # ) } tags = { - context = { + (var.tag_names.context) = { description = "Resource management context." iam = {} values = { @@ -163,7 +163,7 @@ module "organization" { teams = null } } - environment = { + (var.tag_names.environment) = { description = "Environment definition." iam = {} values = { @@ -190,9 +190,9 @@ resource "google_organization_iam_member" "org_policy_admin" { title = "org_policy_tag_scoped" description = "Org policy tag scoped grant for ${each.value.0}/${each.value.1}." expression = <<-END - resource.matchTag('${var.organization.id}/context', '${each.value.0}') + resource.matchTag('${var.organization.id}/${var.tag_names.context}', '${each.value.0}') && - resource.matchTag('${var.organization.id}/environment', '${each.value.1}') + resource.matchTag('${var.organization.id}/${var.tag_names.environment}', '${each.value.1}') END } } diff --git a/fast/stages/01-resman/outputs.tf b/fast/stages/01-resman/outputs.tf index c9e68e66..aefdf9e5 100644 --- a/fast/stages/01-resman/outputs.tf +++ b/fast/stages/01-resman/outputs.tf @@ -150,6 +150,7 @@ locals { tfvars = { folder_ids = local.folder_ids service_accounts = local.service_accounts + tag_names = var.tag_names } } diff --git a/fast/stages/01-resman/variables.tf b/fast/stages/01-resman/variables.tf index b0a97cb0..d0c7416f 100644 --- a/fast/stages/01-resman/variables.tf +++ b/fast/stages/01-resman/variables.tf @@ -165,6 +165,23 @@ variable "prefix" { } } +variable "tag_names" { + description = "Customized names for resource management tags." + type = object({ + context = string + environment = string + }) + default = { + context = "context" + environment = "environment" + } + nullable = false + validation { + condition = alltrue([for k, v in var.tag_names : v != null]) + error_message = "Tag names cannot be null." + } +} + variable "team_folders" { description = "Team folders to be created. Format is described in a code comment." type = map(object({ From 6eeda3da7a859b7efec9356abadb66d7d08d6097 Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Wed, 13 Apr 2022 11:09:34 +0200 Subject: [PATCH 15/28] Add KMS support --- .gitignore | 1 + .../cloudsql-multiregion/kms.tf | 38 +++++ .../cloudsql-multiregion/main.tf | 140 ++++++++++++++++-- .../cloudsql-multiregion/variables.tf | 39 +++-- 4 files changed, 194 insertions(+), 24 deletions(-) create mode 100644 examples/data-solutions/cloudsql-multiregion/kms.tf diff --git a/.gitignore b/.gitignore index 373948e7..4fb44601 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ fast/stages/**/terraform-*.auto.tfvars.json fast/stages/**/0*.auto.tfvars* **/node_modules fast/stages/**/globals.auto.tfvars.json +cloud_sql_proxy \ No newline at end of file diff --git a/examples/data-solutions/cloudsql-multiregion/kms.tf b/examples/data-solutions/cloudsql-multiregion/kms.tf new file mode 100644 index 00000000..34987f9c --- /dev/null +++ b/examples/data-solutions/cloudsql-multiregion/kms.tf @@ -0,0 +1,38 @@ + + +# 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 +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module "kms" { + for_each = toset(distinct(values(var.regions))) + source = "../../../modules/kms" + project_id = module.project.project_id + + keyring = { + name = "${var.prefix}-keyring-${each.value}" + location = each.value + } + + keys = { + key = null + } + key_iam = { + key = { + "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [ + "serviceAccount:${module.project.service_accounts.robots.sql}", + "serviceAccount:${module.project.service_accounts.robots.storage}" + ] + } + } +} diff --git a/examples/data-solutions/cloudsql-multiregion/main.tf b/examples/data-solutions/cloudsql-multiregion/main.tf index 9df7303a..6c893753 100644 --- a/examples/data-solutions/cloudsql-multiregion/main.tf +++ b/examples/data-solutions/cloudsql-multiregion/main.tf @@ -14,6 +14,63 @@ * limitations under the License. */ +locals { + data_eng_principals_iam = [ + for k in var.data_eng_principals : + "user:${k}" + ] + + iam = { + # GCS roles + "roles/storage.objectAdmin" = [ + "serviceAccount:${module.project.service_accounts.robots.sql}", + module.service-account-gcs.iam_email, + ] + # CloudSQL + "roles/cloudsql.instanceUser" = concat( + local.data_eng_principals_iam, + [module.service-account-sql.iam_email] + ) + # common roles + "roles/logging.admin" = local.data_eng_principals_iam + "roles/iam.serviceAccountUser" = concat( + local.data_eng_principals_iam + ) + "roles/iam.serviceAccountTokenCreator" = concat( + local.data_eng_principals_iam + ) + # network roles + "roles/compute.networkUser" = [ + "serviceAccount:${module.project.service_accounts.robots.sql}" + ] + } + + # # VPC / Shared VPC variables + # network_subnet_selflink = try( + # module.vpc[0].subnets["${var.region}/subnet"].self_link, + # var.network_config.subnet_self_link + # ) + # shared_vpc_bindings = { + # "roles/compute.networkUser" = [ + # "robot-df", "sa-df-worker" + # ] + # } + # # reassemble in a format suitable for for_each + # shared_vpc_bindings_map = { + # for binding in flatten([ + # for role, members in local.shared_vpc_bindings : [ + # for member in members : { role = role, member = member } + # ] + # ]) : "${binding.role}-${binding.member}" => binding + # } + # shared_vpc_project = try(var.network_config.host_project, null) + # shared_vpc_role_members = { + # robot-df = "serviceAccount:${module.project.service_accounts.robots.dataflow}" + # sa-df-worker = module.service-account-df.iam_email + # } + # use_shared_vpc = var.network_config != null +} + module "project" { source = "../../../modules/project" name = var.project_id @@ -21,9 +78,19 @@ module "project" { 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 = [ + "cloudkms.googleapis.com", "servicenetworking.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 + } } module "vpc" { @@ -31,23 +98,72 @@ module "vpc" { project_id = module.project.project_id name = "vpc" psa_config = { - ranges = { cloud-sql = var.cloudsql_psa_range } + ranges = { cloud-sql = var.sql_configuration.psa_range } routes = null } } module "db" { - source = "../../../modules/cloudsql-instance" - project_id = module.project.project_id - network = module.vpc.self_link - name = "${var.prefix}-db" - region = var.regions.primary - database_version = var.database_version - tier = var.tier - + source = "../../../modules/cloudsql-instance" + project_id = module.project.project_id + availability_type = var.sql_configuration.availability_type + encryption_key_name = var.cmek_encryption ? module.kms[var.regions.primary].keys.key.id : null + network = module.vpc.self_link + name = "${var.prefix}-db-04" + region = var.regions.primary + database_version = var.sql_configuration.database_version + tier = var.sql_configuration.tier + flags = { + "cloudsql.iam_authentication" = "on" + } replicas = { - for name, region in var.regions : - name => region - if name != "primary" + for k, v in var.regions : + k => { + region = v, + encryption_key_name = var.cmek_encryption ? module.kms[v].keys.key.id : null + } if k != "primary" + } + users = { + postgres = var.postgres_user_password } } + +resource "google_sql_user" "users" { + for_each = toset(var.data_eng_principals) + project = module.project.project_id + name = each.value + instance = module.db.name + type = "CLOUD_IAM_USER" +} + +resource "google_sql_user" "service-account" { + for_each = toset(var.data_eng_principals) + project = module.project.project_id + # Omit the .gserviceaccount.com suffix in the email + name = regex("(.+)(gserviceaccount)", module.service-account-sql.email)[0] + instance = module.db.name + type = "CLOUD_IAM_SERVICE_ACCOUNT" +} + +module "gcs" { + source = "../../../modules/gcs" + project_id = module.project.project_id + prefix = var.prefix + name = "data" + location = var.regions.primary + storage_class = "REGIONAL" + encryption_key = var.cmek_encryption ? module.kms[var.regions.primary].keys["key"].id : null + force_destroy = true +} + +module "service-account-gcs" { + source = "../../../modules/iam-service-account" + project_id = module.project.project_id + name = "${var.prefix}-gcs" +} + +module "service-account-sql" { + source = "../../../modules/iam-service-account" + project_id = module.project.project_id + name = "${var.prefix}-sql" +} diff --git a/examples/data-solutions/cloudsql-multiregion/variables.tf b/examples/data-solutions/cloudsql-multiregion/variables.tf index f4a4a203..b1069e2e 100644 --- a/examples/data-solutions/cloudsql-multiregion/variables.tf +++ b/examples/data-solutions/cloudsql-multiregion/variables.tf @@ -14,18 +14,22 @@ * limitations under the License. */ -variable "cloudsql_psa_range" { - description = "Range used for the Private Service Access." - type = string - default = "10.60.0.0/16" +variable "cmek_encryption" { + description = "Flag to enable CMEK on GCP resources created." + type = bool + default = false } -variable "database_version" { - description = "Database type and version to create." - type = string - default = "POSTGRES_13" +variable "data_eng_principals" { + description = "Groups with Service Account Token creator role on service accounts in IAM format, only user supported on CloudSQL, eg 'user@domain.com'." + type = list(string) + default = [] } +variable "postgres_user_password" { + description = "`postgres` user password." + type = string +} variable "prefix" { description = "Unique prefix used for resource names. Not used for project if 'project_create' is null." type = string @@ -58,8 +62,19 @@ variable "regions" { } } -variable "tier" { - description = "The machine type to use for the instances. See See https://cloud.google.com/sql/docs/postgres/create-instance#machine-types." - type = string - default = "db-g1-small" + +variable "sql_configuration" { + description = "Cloud SQL configuration" + type = object({ + availability_type = string + database_version = string + psa_range = string + tier = string + }) + default = { + availability_type = "REGIONAL" + database_version = "POSTGRES_13" + psa_range = "10.60.0.0/16" + tier = "db-g1-small" + } } From 58ccbec1f1438c6ad582807d98938e96ee80a461 Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Wed, 13 Apr 2022 11:41:59 +0200 Subject: [PATCH 16/28] Add database --- .../cloudsql-multiregion/README.md | 16 +++++++++------- .../data-solutions/cloudsql-multiregion/main.tf | 1 + .../cloudsql-multiregion/variables.tf | 7 +++++++ 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/examples/data-solutions/cloudsql-multiregion/README.md b/examples/data-solutions/cloudsql-multiregion/README.md index 7a70ee98..be44ff8f 100644 --- a/examples/data-solutions/cloudsql-multiregion/README.md +++ b/examples/data-solutions/cloudsql-multiregion/README.md @@ -45,13 +45,15 @@ This implementation is intentionally minimal and easy to read. A real world use | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [prefix](variables.tf#L29) | Unique prefix used for resource names. Not used for project if 'project_create' is null. | string | ✓ | | -| [project_id](variables.tf#L43) | Project id, references existing project if `project_create` is null. | string | ✓ | | -| [cloudsql_psa_range](variables.tf#L17) | Range used for the Private Service Access. | string | | "10.60.0.0/16" | -| [database_version](variables.tf#L23) | Database type and version to create. | string | | "POSTGRES_13" | -| [project_create](variables.tf#L34) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | | null | -| [regions](variables.tf#L48) | Map of instance_name => location where instances will be deployed. | map(string) | | {…} | -| [tier](variables.tf#L61) | The machine type to use for the instances. See See https://cloud.google.com/sql/docs/postgres/create-instance#machine-types. | string | | "db-g1-small" | +| [postgres_user_password](variables.tf#L29) | `postgres` user password. | string | ✓ | | +| [prefix](variables.tf#L40) | Unique prefix used for resource names. Not used for project if 'project_create' is null. | string | ✓ | | +| [project_id](variables.tf#L54) | Project id, references existing project if `project_create` is null. | string | ✓ | | +| [cmek_encryption](variables.tf#L17) | Flag to enable CMEK on GCP resources created. | bool | | false | +| [data_eng_principals](variables.tf#L23) | Groups with Service Account Token creator role on service accounts in IAM format, only user supported on CloudSQL, eg 'user@domain.com'. | list(string) | | [] | +| [postgres_databases](variables.tf#L34) | `postgres` databases. | list(string) | | ["guestbook"] | +| [project_create](variables.tf#L45) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | | null | +| [regions](variables.tf#L59) | Map of instance_name => location where instances will be deployed. | map(string) | | {…} | +| [sql_configuration](variables.tf#L73) | Cloud SQL configuration | object({…}) | | {…} | ## Outputs diff --git a/examples/data-solutions/cloudsql-multiregion/main.tf b/examples/data-solutions/cloudsql-multiregion/main.tf index 6c893753..aeae6e72 100644 --- a/examples/data-solutions/cloudsql-multiregion/main.tf +++ b/examples/data-solutions/cloudsql-multiregion/main.tf @@ -123,6 +123,7 @@ module "db" { encryption_key_name = var.cmek_encryption ? module.kms[v].keys.key.id : null } if k != "primary" } + databases = var.postgres_databases users = { postgres = var.postgres_user_password } diff --git a/examples/data-solutions/cloudsql-multiregion/variables.tf b/examples/data-solutions/cloudsql-multiregion/variables.tf index b1069e2e..941e491b 100644 --- a/examples/data-solutions/cloudsql-multiregion/variables.tf +++ b/examples/data-solutions/cloudsql-multiregion/variables.tf @@ -30,6 +30,13 @@ variable "postgres_user_password" { description = "`postgres` user password." type = string } + +variable "postgres_databases" { + description = "`postgres` databases." + type = list(string) + default = ["guestbook"] +} + variable "prefix" { description = "Unique prefix used for resource names. Not used for project if 'project_create' is null." type = string From e7b1ed71327f60e8c573dff7f6c8fc3a880d739e Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Wed, 13 Apr 2022 13:08:57 +0200 Subject: [PATCH 17/28] Add VM + outut commands to connect --- .../cloudsql-multiregion/gce.tf | 60 +++++++++++++++++++ .../cloudsql-multiregion/outputs.tf | 9 +++ 2 files changed, 69 insertions(+) create mode 100644 examples/data-solutions/cloudsql-multiregion/gce.tf diff --git a/examples/data-solutions/cloudsql-multiregion/gce.tf b/examples/data-solutions/cloudsql-multiregion/gce.tf new file mode 100644 index 00000000..d0516230 --- /dev/null +++ b/examples/data-solutions/cloudsql-multiregion/gce.tf @@ -0,0 +1,60 @@ +# 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 +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +locals { + startup-script = < Date: Wed, 13 Apr 2022 13:09:10 +0200 Subject: [PATCH 18/28] Add VM --- .../cloudsql-multiregion/cloudsql.tf | 62 +++++++++++++ .../cloudsql-multiregion/datastorage.tf | 30 ++++++ .../cloudsql-multiregion/kms.tf | 3 +- .../cloudsql-multiregion/main.tf | 91 ++++++------------- .../cloudsql-multiregion/variables.tf | 8 +- 5 files changed, 126 insertions(+), 68 deletions(-) create mode 100644 examples/data-solutions/cloudsql-multiregion/cloudsql.tf create mode 100644 examples/data-solutions/cloudsql-multiregion/datastorage.tf diff --git a/examples/data-solutions/cloudsql-multiregion/cloudsql.tf b/examples/data-solutions/cloudsql-multiregion/cloudsql.tf new file mode 100644 index 00000000..9e7a5aae --- /dev/null +++ b/examples/data-solutions/cloudsql-multiregion/cloudsql.tf @@ -0,0 +1,62 @@ +# 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 +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module "db" { + source = "../../../modules/cloudsql-instance" + project_id = module.project.project_id + availability_type = var.sql_configuration.availability_type + encryption_key_name = var.cmek_encryption ? module.kms[var.regions.primary].keys.key.id : null + network = module.vpc.self_link + name = "${var.prefix}-db-04" + region = var.regions.primary + database_version = var.sql_configuration.database_version + tier = var.sql_configuration.tier + flags = { + "cloudsql.iam_authentication" = "on" + } + replicas = { + for k, v in var.regions : + k => { + region = v, + encryption_key_name = var.cmek_encryption ? module.kms[v].keys.key.id : null + } if k != "primary" + } + databases = [var.postgres_database] + users = { + postgres = var.postgres_user_password + } +} + +resource "google_sql_user" "users" { + for_each = toset(var.data_eng_principals) + project = module.project.project_id + name = each.value + instance = module.db.name + type = "CLOUD_IAM_USER" +} + +resource "google_sql_user" "service-account" { + for_each = toset(var.data_eng_principals) + project = module.project.project_id + # Omit the .gserviceaccount.com suffix in the email + name = regex("(.+)(gserviceaccount)", module.service-account-sql.email)[0] + instance = module.db.name + type = "CLOUD_IAM_SERVICE_ACCOUNT" +} + +module "service-account-sql" { + source = "../../../modules/iam-service-account" + project_id = module.project.project_id + name = "${var.prefix}-sql" +} diff --git a/examples/data-solutions/cloudsql-multiregion/datastorage.tf b/examples/data-solutions/cloudsql-multiregion/datastorage.tf new file mode 100644 index 00000000..1b75deb0 --- /dev/null +++ b/examples/data-solutions/cloudsql-multiregion/datastorage.tf @@ -0,0 +1,30 @@ +# 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 +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module "gcs" { + source = "../../../modules/gcs" + project_id = module.project.project_id + prefix = var.prefix + name = "data" + location = var.regions.primary + storage_class = "REGIONAL" + encryption_key = var.cmek_encryption ? module.kms[var.regions.primary].keys["key"].id : null + force_destroy = true +} + +module "service-account-gcs" { + source = "../../../modules/iam-service-account" + project_id = module.project.project_id + name = "${var.prefix}-gcs" +} diff --git a/examples/data-solutions/cloudsql-multiregion/kms.tf b/examples/data-solutions/cloudsql-multiregion/kms.tf index 34987f9c..d35ca71f 100644 --- a/examples/data-solutions/cloudsql-multiregion/kms.tf +++ b/examples/data-solutions/cloudsql-multiregion/kms.tf @@ -1,5 +1,3 @@ - - # Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,6 +28,7 @@ module "kms" { key_iam = { key = { "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [ + "serviceAccount:${module.project.service_accounts.robots.compute}", "serviceAccount:${module.project.service_accounts.robots.sql}", "serviceAccount:${module.project.service_accounts.robots.storage}" ] diff --git a/examples/data-solutions/cloudsql-multiregion/main.tf b/examples/data-solutions/cloudsql-multiregion/main.tf index aeae6e72..9fc7fd6a 100644 --- a/examples/data-solutions/cloudsql-multiregion/main.tf +++ b/examples/data-solutions/cloudsql-multiregion/main.tf @@ -26,7 +26,12 @@ locals { "serviceAccount:${module.project.service_accounts.robots.sql}", module.service-account-gcs.iam_email, ] - # CloudSQL + # CloudSQL + "roles/cloudsql.admin" = local.data_eng_principals_iam + "roles/cloudsql.client" = concat( + local.data_eng_principals_iam, + [module.service-account-sql.iam_email] + ) "roles/cloudsql.instanceUser" = concat( local.data_eng_principals_iam, [module.service-account-sql.iam_email] @@ -82,6 +87,10 @@ module "project" { iam_additive = var.project_create == null ? local.iam : {} services = [ "cloudkms.googleapis.com", + "iap.googleapis.com", + "logging.googleapis.com", + "monitoring.googleapis.com", + "networkmanagement.googleapis.com", "servicenetworking.googleapis.com", "sqladmin.googleapis.com", "sql-component.googleapis.com", @@ -97,74 +106,32 @@ module "vpc" { source = "../../../modules/net-vpc" project_id = module.project.project_id name = "vpc" + subnets = [ + { + ip_cidr_range = "10.0.0.0/20" + name = "subnet" + region = var.regions.primary + secondary_ip_range = {} + } + ] + psa_config = { ranges = { cloud-sql = var.sql_configuration.psa_range } routes = null } } -module "db" { - source = "../../../modules/cloudsql-instance" - project_id = module.project.project_id - availability_type = var.sql_configuration.availability_type - encryption_key_name = var.cmek_encryption ? module.kms[var.regions.primary].keys.key.id : null - network = module.vpc.self_link - name = "${var.prefix}-db-04" - region = var.regions.primary - database_version = var.sql_configuration.database_version - tier = var.sql_configuration.tier - flags = { - "cloudsql.iam_authentication" = "on" - } - replicas = { - for k, v in var.regions : - k => { - region = v, - encryption_key_name = var.cmek_encryption ? module.kms[v].keys.key.id : null - } if k != "primary" - } - databases = var.postgres_databases - users = { - postgres = var.postgres_user_password - } +module "firewall" { + source = "../../../modules/net-vpc-firewall" + project_id = module.project.project_id + network = module.vpc.name + admin_ranges = ["10.0.0.0/20"] } -resource "google_sql_user" "users" { - for_each = toset(var.data_eng_principals) - project = module.project.project_id - name = each.value - instance = module.db.name - type = "CLOUD_IAM_USER" -} - -resource "google_sql_user" "service-account" { - for_each = toset(var.data_eng_principals) - project = module.project.project_id - # Omit the .gserviceaccount.com suffix in the email - name = regex("(.+)(gserviceaccount)", module.service-account-sql.email)[0] - instance = module.db.name - type = "CLOUD_IAM_SERVICE_ACCOUNT" -} - -module "gcs" { - source = "../../../modules/gcs" +module "nat" { + source = "../../../modules/net-cloudnat" project_id = module.project.project_id - prefix = var.prefix - name = "data" - location = var.regions.primary - storage_class = "REGIONAL" - encryption_key = var.cmek_encryption ? module.kms[var.regions.primary].keys["key"].id : null - force_destroy = true -} - -module "service-account-gcs" { - source = "../../../modules/iam-service-account" - project_id = module.project.project_id - name = "${var.prefix}-gcs" -} - -module "service-account-sql" { - source = "../../../modules/iam-service-account" - project_id = module.project.project_id - name = "${var.prefix}-sql" + region = var.regions.primary + name = "${var.prefix}-default" + router_network = module.vpc.name } diff --git a/examples/data-solutions/cloudsql-multiregion/variables.tf b/examples/data-solutions/cloudsql-multiregion/variables.tf index 941e491b..3764f4e1 100644 --- a/examples/data-solutions/cloudsql-multiregion/variables.tf +++ b/examples/data-solutions/cloudsql-multiregion/variables.tf @@ -31,10 +31,10 @@ variable "postgres_user_password" { type = string } -variable "postgres_databases" { - description = "`postgres` databases." - type = list(string) - default = ["guestbook"] +variable "postgres_database" { + description = "`postgres` database." + type = string + default = "guestbook" } variable "prefix" { From 9d5df771a36a89d691bce3893135c9322cf51532 Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Wed, 13 Apr 2022 14:42:11 +0200 Subject: [PATCH 19/28] Update README --- .../cloudsql-multiregion/README.md | 21 +++++++++++++++++-- .../cloudsql-multiregion/outputs.tf | 6 +++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/examples/data-solutions/cloudsql-multiregion/README.md b/examples/data-solutions/cloudsql-multiregion/README.md index be44ff8f..8c2c05b9 100644 --- a/examples/data-solutions/cloudsql-multiregion/README.md +++ b/examples/data-solutions/cloudsql-multiregion/README.md @@ -32,13 +32,30 @@ $ terraform apply You should see the output of the Terraform script with resources created and some commands that you'll need in the following steps below. -TBC - ## Move to real use case consideration This implementation is intentionally minimal and easy to read. A real world use case should consider: - Using a Shared VPC - Using VPC-SC to mitigate data exfiltration + +## Test your environment +We assume all those steps are run using a user listed on `data_eng_principals`. You can authenticate as the user using the following command: + +``` +$ gcloud init +$ gcloud auth application-default login +``` + +Below you can find commands to connect to the VM instance and Cloud SQL instance. + +``` + $ gcloud compute ssh sql-test --project PROJECT_ID --zone ZONE + sql-test:~$ cloud_sql_proxy -instances=CLOUDSQL_INSTANCE=tcp:5432 + sql-test:~$ psql 'host=127.0.0.1 port=5432 sslmode=disable dbname=DATABASE user=USER' +``` + +You can find computed commands on the Terraform `demo_commands` output. + ## Variables diff --git a/examples/data-solutions/cloudsql-multiregion/outputs.tf b/examples/data-solutions/cloudsql-multiregion/outputs.tf index fb945ab5..50852670 100644 --- a/examples/data-solutions/cloudsql-multiregion/outputs.tf +++ b/examples/data-solutions/cloudsql-multiregion/outputs.tf @@ -32,8 +32,8 @@ output "project_id" { output "demo_commands" { description = "Demo commands." value = { - 01 = "gcloud compute ssh ${module.test-vm.instance.name} --project ${module.project.name} --zone ${var.regions.primary}-b" - 02 = "cloud_sql_proxy -instances=${module.db.connection_name}=tcp:5432 &" - 03 = "psql 'host=127.0.0.1 port=5432 sslmode=disable dbname=${var.postgres_database} user=postgres'" + "01_ssh" = "gcloud compute ssh ${module.test-vm.instance.name} --project ${module.project.name} --zone ${var.regions.primary}-b" + "02_cloud_sql_proxy" = "cloud_sql_proxy -instances=${module.db.connection_name}=tcp:5432 &" + "03_psql" = "psql 'host=127.0.0.1 port=5432 sslmode=disable dbname=${var.postgres_database} user=postgres'" } } From f8e86ef303300d6eb9c7b07b7693019d7b5fde97 Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Wed, 13 Apr 2022 14:42:37 +0200 Subject: [PATCH 20/28] Update README --- examples/data-solutions/cloudsql-multiregion/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/data-solutions/cloudsql-multiregion/README.md b/examples/data-solutions/cloudsql-multiregion/README.md index 8c2c05b9..a388b6b6 100644 --- a/examples/data-solutions/cloudsql-multiregion/README.md +++ b/examples/data-solutions/cloudsql-multiregion/README.md @@ -55,7 +55,6 @@ Below you can find commands to connect to the VM instance and Cloud SQL instance ``` You can find computed commands on the Terraform `demo_commands` output. - ## Variables @@ -67,7 +66,7 @@ You can find computed commands on the Terraform `demo_commands` output. | [project_id](variables.tf#L54) | Project id, references existing project if `project_create` is null. | string | ✓ | | | [cmek_encryption](variables.tf#L17) | Flag to enable CMEK on GCP resources created. | bool | | false | | [data_eng_principals](variables.tf#L23) | Groups with Service Account Token creator role on service accounts in IAM format, only user supported on CloudSQL, eg 'user@domain.com'. | list(string) | | [] | -| [postgres_databases](variables.tf#L34) | `postgres` databases. | list(string) | | ["guestbook"] | +| [postgres_database](variables.tf#L34) | `postgres` database. | string | | "guestbook" | | [project_create](variables.tf#L45) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | | null | | [regions](variables.tf#L59) | Map of instance_name => location where instances will be deployed. | map(string) | | {…} | | [sql_configuration](variables.tf#L73) | Cloud SQL configuration | object({…}) | | {…} | @@ -77,6 +76,7 @@ You can find computed commands on the Terraform `demo_commands` output. | name | description | sensitive | |---|---|:---:| | [connection_names](outputs.tf#L17) | Connection name of each instance. | | +| [demo_commands](outputs.tf#L32) | Demo commands. | | | [ips](outputs.tf#L22) | IP address of each instance. | | | [project_id](outputs.tf#L27) | ID of the project containing all the instances. | | From d3addd0267fc6ee994ac84017c60d52760e98303 Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Wed, 13 Apr 2022 15:02:21 +0200 Subject: [PATCH 21/28] Update diagram and README --- .../cloudsql-multiregion/README.md | 6 ++++-- .../cloudsql-multiregion/diagram.png | Bin 23721 -> 38675 bytes 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/data-solutions/cloudsql-multiregion/README.md b/examples/data-solutions/cloudsql-multiregion/README.md index a388b6b6..4ad07713 100644 --- a/examples/data-solutions/cloudsql-multiregion/README.md +++ b/examples/data-solutions/cloudsql-multiregion/README.md @@ -5,8 +5,10 @@ This example creates a [Cloud SQL instance](https://cloud.google.com/sql) with m The solution is resilient to a regional outage. To get familiar with the procedure needed in the unfortunate case of a disaster recovery, please follow steps described in [part two](https://cloud.google.com/architecture/cloud-sql-postgres-disaster-recovery-complete-failover-fallback#phase-2) of the aforementioned article. The solution will use: -- A VPC with Private Service Access to deploy the instances -- Postgre SQL instanced with Private IP +- VPC with Private Service Access to deploy the instances and VM +- Cloud SQL - Postgre SQL instanced with Private IP +- Goocle Cloud Storage bucket to handle database import/export +- Google Cloud Engine instance to connect to the Posgre SQL instance This is the high level diagram: diff --git a/examples/data-solutions/cloudsql-multiregion/diagram.png b/examples/data-solutions/cloudsql-multiregion/diagram.png index 6148a53ee3391753fdae21b1c6e729761e220fb9..e32543c68729173a5c5f3ef15e6df457863b9b93 100644 GIT binary patch literal 38675 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 M8%Hq7+UMba0qAIJTmS$7 literal 23721 zcmd43byQSu7%qyUARr(T(jbj=NrOno4CMercQ*q_{}gEu=?3Wr>F#ca?rs=*=(yXn z*8SuBbK|Ug*19g2n?d%>_r2fyKF{;UCPW1+i;YQ&iGqTHEiWgfhJy0^1_kAr%B$zV zcZ%pQV}T2*%V+s7uU@@cSW{jFUXsA1wP5NF<}i07XEPKFdj~r+HWw3TGc$V^O9$97 zYMU4e3N?znl*E^>sfSAzU&&{D#BW!Uk`PU^dKb$Q&kY%8nWH`sl(MVFo5;trCLDBw zer|M+ZXDb@EFw0A23)hG*gdnlHLBPK;_20Bm_u7)s8POCOI)-TG__x*IUj}PetM(z ziyGt2=K7t-_4C9uQ5Uquq?@B8mr>49!HyA7yqUDBc@q(^);c<)S{C4n!s-9Z6}V8R zzdryjpF||5pP``q_cgEt6?l328j22F)=4UHfJ--zDFtu|w&)iEE^o&E=Nn_po1QyX zsX-`lqF7_{@Mz!Cj~^s0e+nOQ8v%a0?0F@QuENB1KPMIG>Vr>FaZuor2sy(yuBw2y zF+CTr<5NG-(Z|4f7YJXXpd`LErMP~zv$!!$dN9>Ux;5#vyxhChUk^!~`@pJOqSN@9 zNr!23J7$@ah40^w3-tRjc#P2jQrLM-=m`D0-Lb9mM#9MEggJ ztYz9`Z)41Bje!Ntpd*An(aFZf$ZokWtBp;mnR@GtN~OBg997RR{zl@UQQm*?jDAF$ z#6v+@ou@t+_9!YM7o>h#GplKd`{KHPj5TO`$Lz`k^|56e1E<#F45hkAt4e165?@U) zlg^)jvJqv&sY7Wb>nu@F>OK2~T+*~%)O-(+LoX?M8J5Q}u>wm4BimvgeoULH&_6a{ z5Bn~q#R=}{mzQT278V!xQN8-wA_o1ri$qJAGOkEGcXkO2#bh-gFS>Q{w@0~%pw+O@*c9V!L+`I_-@-@og&fZb{Nu~8gHez|UQj>feJ zPgT`;f2N|a+R`?Ha~D?Y_Oom<@K}+01WwYkR3D9=G&!{FeYMmXW$YXeDE5-e!`;8G z6!{?E)LW1LQ_6a55;5OW*V3X6fv6IUkB^_9JC_n21iRp&RxVJFG@R~1P7uZH@e!UFfgmYVt>?rrJ}g1Sm%uvbgwx8YmkR~TscCO4DS1OLxn=bH;IfZ0|nch4rMSrhZY=O^itc?1qVTX7}-63#)l6JpAj5uZ4~) zs5H;YeWj*?Q(T;nUa%v*J-AsvqqwfTqR`{`(LXM~Nb?J;ix52dIf|nz;5$W*b)&5@ zrA7FM&m_wAZw;#-n%ko9WqJGbw0kw>8aLMmiFHyHBU$kuE!t$Lma5iM7oQ2u_KWK~ zx%%gNi#;VZR*kT+rJkQv-ng&0%4(SRb{`3NwzSq4{BmJ`_4z)ccfP{rYHyw=eD}1E z65430`hDbMW28ngpQtFCxHvx-m#W6*MfK!d=?9ed(pOLmcH?iJnRg!MkH1J-c)0`} z;|ggvMc<>PX#Ih*TyxFj{!K*EeLMP)T{<;++;W{~_@g#sJN!C(=%v%3UXYik7a3Vf z*P5KC=lof&A~(-C=eP^4;(2} zRSZ#bo4OA-$@4r95B`;`3nkMY^gTlf9IoKyh`O^c$l#fTsHj#q#D^F-u?t0SzrcWE z_UBBMhnwD_@lh`^HJ;ao)`LP8>`r&pC(lNk=SmX@rn{TwW5Ja zFV|vl9gU^nieGiS`f9J-tE+2Y`e(u`6eB<*5J*0i$L6<}X1Uo;`E_;2?hegUa5y1m z5J%bEqH|-d{Y093Nr9H9Q6yh!aq-=f-9n1Cc4y|FBCGI<+RIBG*BvQUnAc*DwDcS_ z3BQZD~8*u+Iq(&gO$%5HRUICb`Kr=WwaeUzDp1hjC(x@9|_ zcs75w_CMEG^dU=0)-}@XRuN>-1}*r>DLz^2!ex zFh;bw^)_mS>N;bkbsi2cv`!u{Wo2gPCcU6Y(?j)_q@YOCSy?HYIUwTD{v_q&^Wf0d zgwXZz&$A*56BK4qBnT)wN`L(JjAlJmcC)|xHPKJ0O9~O#>fg0b)W2sA!Z2OPab(>i zi_7bdhm(X_z)yc4sL1TNN%+>0H{N2S9$>e4YF)(?BD|aX$1d5Q$4h=HZ)U$&jzd^j zY(LsW7GGV54CZaei+Wuh&{-|b^GWHd#;P!}vS8|I`+y)_QLj$1_zwZfxf2#EC@dv> zv37~HdB@H)bKp9{W_O26KJT}xB0%0)>-ijImu$Bn5#70lo99`*!wP4bob4NfRnZG-mW&1j zrVMby`-98t$~m?bQUArA-ybgrFR!31Cagh)I+0sLHMO@UtZze_tB@_gzOZp|+b)~< zTagjv^3fNMqk47-3aKwDzn=D#Mb}^g#I$YVf4o*6aorGm2L~&s9YFyvJ-W5bAjr8Y zq|%$g!EN!|zo>=G15%R!$8i=TsuBaV)l0P|@t ztrTDYFa#1uK*&Hh`#Mk&Z5ZAEn`N`(EaJN(TZB-mOG2^xRl1LmympiM6zY}T^Z_!w z;`@DbR_FILD(|ls*8_w5$*HM_lNjY!1{1ArzN&m7kYqt0+pWsPt=%#JJ3B7KmaQji*dVr%rOnt~3JoHEw zof;#J*OXMsV!m>ncruxM%5wb~*0?b^#YOzuyecX9dB^XSC(LFVVbj4=b`TA7T;mW zN&H%HZmzD>ASEM#%te+LWMglIIC9#9jR!8wu<2Fyn{qcSrMKMSri!l+hxCVLO;4v$ zP?Gqqs2D!awzMF;{lXtRVnhr$6;Z^IFh6lbuVs*>Dgh7AIKauuii)lcp4oy9 z9|)waY;7-JJW5{qiX+bibnkuK(~Tcp=<`c?S>NRw+|p5qLMEGw z027HsHZ4)+pBpDoNEqTSUsa$V-_6d}rHVk1JMJq72M2Z9Y!43)nU2B1MC?o;xjzwz z!>pJbzG6)`H&>R>@xQYOP)tg43LJIM1AAv& zp9`=@czi!`ZXVsc&7IonTF@e1#+bMWcNUX^hI!zUy@QqAg8QoD(p*)CVKA3@6?8~p z$D!HICu>6RDB@!Z+dM^+pW~k&HtSPg zEkjiYMMVygcf25${xPyULrfau@VJo-_v_sX!#8ymD61OZOL&4f%@VB=7XMq#^4B4X zu_SBDv+J1*n^LS6F%VesIyEk`aBJI`uOMJtw0u>{>@??NbMfN98CK;)5&Iws@?O;Og{ zs5#^4oKwH47hd1LmOg}{pqOJ-j*@rE(T}r3>4?i_Z@CE@F%rbV-XGlMczAde6haq- z{+&}CgpY(|Z_lcbYXfJexv3Ab@^|;@F6J$M6*tc<8rlnnH&J5;nh?9i+w`?HOAG3% z0o!ZK;i#DdiiCDv(7DHh6y2*~TVB!#8Pzug(x-R#$$1e7b~f2fLnb#(Liy+{qoLy~ z#pqbWDsyx5GM%cTX4_V{K}~7TV$E&5>>yrBDzjHn_)Sn$_(?^DZYI1 z#g>_=1xA()Sw6tE7Z&Oe4-{!>Y2T??bsA^a{^aN9%47t1v{nr8h=^R0l4AzhSX=Xf zOu`9?HuLjxh$Cb`6F}k@xR_o+e|P`5Kan5O|C`^`%uIWDJ2rMRnAN?>v(Za8d!tlf z2Nl&nEo~|0FksHUPS#&eLSoq>-RE}54|%gS6feauBp|@cy`GlXo25}&ASVx2BS0!2 z+ajolCyiYf)>506(qrg!%z-=iBdlRh_d&KzeaKun_!Nh z-6zrWjO}?o{r1n?Q?RoQwglZwq|hJ?TvKePYSdwOFr7okSXktFasn4`zR3ridbI4Q zBQtYtZ4LMr_%1|1j}(5`U?VDVR4TvA$G?#y0eW8)Q-z$APQP-bRO zN@C(gztxku+wp(%g*{x7}@Itk@mzsxX)e&{S%(rSLCpInIt{ICyW2h}TqC1Kc4d!$bfC zzSlvdpYK-9O56GJrPIgmq-dRPZ2=rohRj4`AZ(n^SGwzEm>JH3kuG8 zwv3u`%SX>HtgI|7ykiS;Szee~nOrL;Qu{GQp_*_tbvR)Wq{+TNQsv}iZ zUfl-~V&m^wSk9w@DEs^SscCv=5eGAuxUZ|-VMoG_1%(Y}OO1TyF{ir~_&a1k>ZAB) zj?!~=+0tdG@2H`vA$MwPU(~6nsHMtgBM>R$fBFRkva_SWqh}DBDbY`gP8}9BXvoUS ztZ2xtI_MryXV*L~A|zBY-d_tZz_KPff=l z1mNyRhsI)RaffPrc_*i}yryFT#}hdhMMX{bF{{PkiDeHqx3rlzuvmn^KhmMIjT zsZTk^*<<`MWx5oa%QQHkKGoIL)pT`4Rper#^kSt=2<y^2RU=59z&%X`0y+B!I_))^sCvfyT%7my0Ms!0HerfUIfY705sjMHOa zY@jzqRJkDs&nm}7?{Mlpbt`FPX}B57X7*r0dTPewz1Rn8(J|R^lLttwcd?4myxQj4 z)`J^5l!9(ql@*21cryruUcORmcq{ijfkVfdQ#VnieGj%+>l@KwX=!gCMa#`)&Q1D` zLwgk_v_CpMRB2nKPdp3}D(^P0p;S=VM<8Ohf~jA5w)hULOZP|zbT`cGOpx^|=K?jz z3F8E-_@I;@tV3K)p1RplUB)|4EaBwjs*@CJC7S-yqT1nWgW27NNpncoJdhQdU{(4@ zHUhDhpW%%xzda%jdgY_PL-9`@XnVyl)luWqM!(| z;na$XF`k{&p~}N6^vk5Zc4!t(70$+YlgQXcKQ2T4!hl;u;=}9P zLV7&}$Z=jguVf{=Knv?Bnb>2n*M>|PG7-Fb@q$5%HQw^c9x@Sp?)0*#Xdm|l>bL*s z%&R;;IN-hL0Jb|)v5-cC*HBel{OG+A>~0Dagc=0rG>QX) zzd$I6$q$d5Fut$;VVp&{_0D$xl9CGN0uq@S1FtIC3j{{3{KB`aDq>|_c23|(8NIY_ znN$f%iNcWMH(!m9!k9uaKH7XoL_-4@u%b{<_}Nlxw<#GcM?ps0#H;xPTjW@t0&UAA z>U7`EVys;87Mg$2Lmk%QKSLl3!a_i`mY#DPc?(o^lH!w#0P1odJB`Wr^twC5zr zDtZK~sl)WfN{dszbr>?Se*hR>MBV-9Vvh$XE9_ZWCFeB&A8>n1FQ3d`UJvsffGov4 zNBM6q;8CdVKdYm1p1{GH%o9iD-{s6UWM82dD-dav`r;Ul@1Gu1P6Td_t*CQ{x|MDxhJrO7y+N1uL`YB9n$as9==3b6{&KZ{#?NrjE)be^{qU$F z&`C@ZArm&o+PmM51z1?nG))KPUl#}{ZQFfAzi_Qg_~p7Dq1^SvjDj-E#vsu!Tx|KTHKx z>CbN%{9+35lT~f%gZ3@cO+xHTH(Xq7I)NC^`IIIdGOQt@x>RAr6%L15rS0m&`^9MIwQMhaNRU|d>>+*F2N|qG%c5g@yVpU_8LIg5>v=`<4KH$k zugrJ@+U76NzZp4VWzsp6Q^3CUab?5}@P9^QT~KN(>SunMEDXCnK*+s7L2*F`x{o+B zyfklL$Vf@FITVyzbc_DxCoaXn`4%Ge#l_UYz}-vRP2bwQ%zbvr@iwQa&3*5-iP%JH z^XM2tXjWZ4Z*u1~pE6N-9?esU=GCN3wbaMQMq3^n{Q22a1#X>lnb{_xV)W*|rC`O* zL!A4fMWq&gs=S!QiC56b^oy9D54!3*?}1ij)h|~=wW1;z?-YI%t)Lyu$kX0vu96R) z0lfQN;{w)D0zn){1iO{kfu)f8)Wmdbl7UEBb!%B|`sMa|z)&`SE#^=>`JJPUx*o*S z{=G@b=`ge4TYZ$^1D|PXYWQ;90^QyAsn65j z!oELSxk<){XV9+wYAROqesldu^dTQ@!9R>ub( z7FsHW#|>q-arbhchmJxh&Lh9FV*G3!t?jNJw*32^Mn0cK|IaYbtT=uX24HMVz}R%M zojywlpvUz>wURM6cAF^IbX%#`_o6jAXCi_QBis0f- zN1w3!~g(*e7n@kn`>bdi1wrEsHunXl$1o23G2n*TkJj1`5wcd#C zgfG%Y;V&Or6^V*M^9qFgjTo$fTV2pw<-2MW2l}IE*8rQt=^vI5QI1U8cI7$Ytb*i6 z2wMuhET&4Y-wDA|^3HVm*hnN(sK~7~GII&N?uqNxg;&uj6s`++oTG>3%r~0^b+zfK z`LDHSf5<~-$wOL=+`IN-WU5>}%j-1$&sl(K)H5v)`YlmAP)#51kLSL!&en7U)y~Oaow#; zc_9*6rxQ6svI_W<^Jpal;CWFEhf*@GG7*&CiM z-BWr1mInYd3Ac0*IXH5+N>2WjD6-)C4COyTY$&>U_wV8b{`JuhOapA}DTqeE&r<{c zv1&?N8*rrUtc~7&6vO5JZ^s$__kjBU-2errjct~DZ7~YxU2~CLZSfma9A%W`)-HUg z#S!~aG!V?+PMm_!ZYK|L z`=16)9C5MAz?=kgUc1BHTPz8LjDmJj1emf#Habn7XlBwi3&XkRmFF&M*$4Hat=Vzd zVS{HPU9G+G>2gx?u=x-11cHb0aOXhT=TkLoKB!&cs-D{GXFUN7*}j#71h={iG69~-TMCF|}-u~SFsL4spm2~ma!wPGA z=T&C_M~kpq22f&EkmH=0uJQ1Z2YC%m)ftC#lRo_eYob9V$6FoL!ca?ryk8%YL3c@+N* zWtmZUZA_RRf`yrX7AM#D5mzFQCqTc6CadH9u~*Xvq-Ph|k5Ka$gzwH> z)X$yOf)nqDOhC@ z`~Yj&>*_3=ml2N*`m4>veQIDouQ@gLMT+1pQPqIs@zM~zJYCj{;yqJW^*S4-`OB&& zxis)wss@KRiE`SklJIW5J8M!M-Sbdx6AgwAb6>;rP|M-L5%63?{=SlrY)^Dn(gQxq zVz&lc20&{L8Tk#R{K7m|`c6c6d^L^p!gmSmtXj48oQ2|Hq$@gN@qiW#fq^0=6GbDI$+wAG)!Gx#v93|$HA3!%{ zV#;P97qGc=^_$QN{PcvJm>eUJrYC>| z{##NCr>V!9YxEwB+8yXdQ7@C}XnNeCT{Y);*Lb%3woN>T$(pw{JT#p>Cy4Ndy%!iK|j^2mbzkR*xJL@mu}XPbi+Ne6WuZJv+f9=pwS=S zqwbChIf>bG@W=j$cbHG_v8P|U3c>ApykzdV6yE^ii?nUOYhni%Pc^Gc=)voyBM9&k zZO73o3L$(<*`t*&T(_c;E?}u_Y&O-buct5%kI<4gQB%gGcMoG$b&27gDI@pqB9}il z{xzLM;{H@(l^}QtYB>u;S=V>9Q`8*$J7`8o zg){8mhkePgLhO@h*EG|a4JS>Sa&z_u%CcK^lANN2^jmZ3Rw%^`e3BA`4w#)+GPYWC z88o~o=dhFz7992{6T2#$U~8CTHgV9zi|Vl@K7A>;lkIZrG%B0tHmN(cX7!OQxc>l4 z|C}8~iDT2g-nvZReQGa$@i6vfZbGWOYjk4N_TeGr1$t*T))*h`ttwb!kT49S=p+}d zwKCh&$NRkDcCj)Gd>P;R80KEGNSU#)p{VStJJpKPoutB4d9_Ls2Ao&&nG?sBY5tEc zXB*j--RRoUM-`^%lsF|#XGoBRLZx1GcSXwuP33p!s1Z@79(oyAfaX{f7KoRdFGSW0 zvr>X-#k{oxV$osA$#^HdXe$g0#2_g<)7}tR1}qi6JeZIZmj6EbNUz{jvbW93o!^qk+SoSuZfHeJ@2Xwwp3Hle}cvuvSjbS$2B zKWD2Z#3bp8apY$f-uxnSbLma36~=T2My)T7@zbH}&}owhb5P>Hx5{afr-(q71cX2PDUdgB}5Y#U&z5>gtY ze6ZSTBH@mQe0EKFFxZ{+BjEdPP*3_b>`%Be@?n z@E=pQ9 zD20PY3osNcczP1B6G>FuCu&SiNMKd*4A|Wy6+mj{ImG=u-F9%?f*le01ao1`zn$? zk*~a9TT5n;Le_qOWY@0lUvnIGlintZwAh5{RgIXZAwt_nz#5yZ4z)DTLi^SnH@CN!ujdz0EJjm1vX>l0q#>>{77_#N zfK*sK1d}g_FF+E*IxD}_78uvsJmYVgPmZb53$P)$!>In4GGbr1vRbrAtih5@WG+kY zS71mAWfS;p@ombwXAMIWcA) z`A+eQ15a_hXReI2U6+4%BZzEr->qh-ln-frcQ6$fu->AWL*F1EV$}Qo;FHL!0dM!T z%AFFSx0|Glzz4N--7hKJbny&|V)f)t@8OqTqhWAjeTl%~V-g0l_=}ZUzawgVxcArI zYWHvjMV&MVUy$Iy3jQPcQG!~>Ici1MO{P0v`yqFz6r?M$v59pmp04tfwv$|%~yH&XA7mKXEwiZ!%vO{mhp zaX$S4ms(@lK?|FzbQRz?cI?BH?MFsBIwj3D`?|Xj#`Le7QL?S~TO9Z} z`zc))rWBwFG<5W_G~pfRhVMXI)mu9>Eu8ul22i|%LUVI-iNXT7ybUtKzT@f76;V29 z9C+~}6f){5ai4CyqQ+UbM!Bs_l45OBPRE8_#zftB|7KPwvJwq#kEFCcARHJO8I>=~ zEYdy9O-*wuE1mO-(K)keFsG`E`%>bnmbA4RItL|vA6L9+sHU0SV?*3y<)L7sJmr0k zmW%PW$Ah+?deO15)n-HS<$ezb=2bUBSegX6nVFe&b^Nz1zz#YtOw%48AJ2EjG$9b| z*ROwk&LsQgTB2d&=hwy*`ts#VoFEe!esq2^NOrOtvW2w4Juzsrur&-n4Kbd4M{_*G z6j}D`2wE^P=3TXo#nJp`WL>}fYPQ0}q4`wC%xtUaxYedY%ed^Rhr?GjKEGKJg`X;7 z!Xu%!RN;tt{P`M7IaN=H_HFg3gxw4YvXy?EcE~j_i3)OpEca`vu8s``jO%{9{IDLz zF+DxqdN#;1arZ!ijvO)c1Yg?O+41r50p~iQHcx1k{E?gcEoMM)zi|aHAWeZ6aNB&3 zh&f9FLn7cz;PCzei!EqpGt9}aWgYj_2_$Belzdn1=f!d6TXTCO3_Is}5#x*2_St^E zTAqLd)^dN{C!U^=a8;G@a5$xD@X-0L^^MB|aQ?>IA5b~7P2*&~-=`_uNQHeUu9)H4 zEf9ccCDI>>6<>e$UQ}f*o)r82;H+u7z`XU;sNTlvXO3Upu7%x9D5#3E4OfHo21iiU=!%RW<0z~@@Y>JEd2hlek&B05V{_^c`Z{^Vuq z)2TM3bL-0NX&r=|M#zozW#E)_`_bL&!GuCVcGW%A%4GxYw)nI>314tz{|_qe7znv zCjQh+ColeVGbUSn)rrMHE(F_Mc|@$RyCh&6(u@`kl~P!J!R0G>bUf^u$g!Z`>)fYt zQmwW83-=$&y()^6+=-)94o3vO(6a@=}nFDWU>d*C=~ z;IS3Qzx*}3yqtrD>3ef%yd%avYYyIQy4fboa?U9&shm6O)j1MdORUae z@OYN6GnSz&m7E9Qk0LOx zP(=o#(dEyfBa!N2oqkpp-&@!!TZ{;&fDvbV?5(G58Ot{sJ}}h~KR8}%3K6V!jW53NIx*$;rGzX-+Fqv;IQJp;^V&P)b|Y){Soju z?f5Og3B!Y}ZERRrSnBK++@2n;IG~lLJELj;yzasG34mArirEVEnxdkNn_&xZnh^4@ zB<|ME4wabqHQ@NufB*h9c-%603qRfH%kbDvDk&*p(*gRqXLl=4kNqqC`ZIb%@~Dpy=`LE~;0|?&6#VbF-Q=C;;QRw7s0+ zc4`|CB2v|!$bEPK@Ta`>cHeaAy&2T^W-B3E5A$)q{Ly-&H$vF`2w)^Xk1^4!H*n3? zv$zEZfIaF~ewwH8uXs;voZjziwrSVdQ{edk7_jumsH?cVm2^Z%HB#iPAJ_N9e|HS< z|J{S~wpQ1JY0K%5%euS89Wj2%CUNNxArd16R=snjXu5>poES^_vc$B4DOan<$i_X+ ztQS09-nrG4Dyg&EM!yQ^^qAylR@$U)$8c#Fbtd6m>V~HLJo#~Zbjh9>%?I%L`CA~w zfqox0*~d5VyW_Se;3WOdQ&#TB6I!LDfaNcmM2cRXXFT0U$x?=(y@SRZcp+-&72~B0 ze0DQ~d>;-B{I+>AD&}kx<_2{fT3pw=L#Q54f|Ny(1b99O8-u&`cWu`kPiGt%x0Cq* zj{>x3Q#^IsC;rGK#AFLV0=Qg=L7YR;>l~J0c_R1@IZA;e**n-I3 zSxY!c_8IHx%~-NqhH5Qj2-K2H7rn%~_+RS)fELQx`Cr>s4Aj+Kwuh5KLqjV-(?F=4 znDzS5(a}ly7HToEs0Ki{n z86x@L5NYG%KSaU9jt{-F4K5ZJ=q>sgQBkLnPuG#adBuX=BWRUiLTMd_#CH!``4d+2 zJx6H(nLpjNJ$darKu9el^Xqs>Whe3K;nvb0; zYVf!`D@Rf;-3c9*4uln2P5{f2`t|{{$G3bN+XIPLq|Zn82>r2)mex zVN`CtKO2gOhycbo=Vi-$aMx&o29%<)6cOtAi|cQmHJovT`50M_4OQ7$XS5Of(;v*f zey;nEq{e2ZtYW8z1frvJ*Nv}iYHluuoK*Ml@bGMT1Q^K5-TlgJxe}0R@ASVK9ZZ)- z(*aXc-nSDOtz3|RdW-3*UrMs1lZ$j3@yx;NTR3C8tZyC2HAS9*;Pxs&Qu$BZ!JV|? z;^HF3AFmyJ&r_%#c4grS&%gaQU`iSxrdq674hDfDpkH`-BGSZo86Xg(=YwhaKRxxd z^K_p9#G}aFVO3#aVHT4Z@P%fLEGG$2Gv(tX+IZbB7+|VD(<2;segFD>PcH*b{#lTR z3)PA~Z6uC*cWTU{&i!TCSz z<;l2Fl9$K8%gxDwxw<}_1c|2_W59I=0EL}kz$Pl%($eB{_*PoAS-*V5Jce%XW+#J# zgTrqxH$i5>o=g1*)-Bt<20{rWT(nO1D+jxvTl({2u

XF%V}J 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 BdGP=M From 15e5904966ee9bbf1b173817780b855d3e6b561f Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Wed, 13 Apr 2022 15:08:37 +0200 Subject: [PATCH 22/28] Add example test --- .../cloudsql-multiregion/__init__.py | 13 +++++++++ .../cloudsql-multiregion/fixture/main.tf | 27 +++++++++++++++++++ .../cloudsql-multiregion/test_plan.py | 19 +++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 tests/examples/data_solutions/cloudsql-multiregion/__init__.py create mode 100644 tests/examples/data_solutions/cloudsql-multiregion/fixture/main.tf create mode 100644 tests/examples/data_solutions/cloudsql-multiregion/test_plan.py diff --git a/tests/examples/data_solutions/cloudsql-multiregion/__init__.py b/tests/examples/data_solutions/cloudsql-multiregion/__init__.py new file mode 100644 index 00000000..6d6d1266 --- /dev/null +++ b/tests/examples/data_solutions/cloudsql-multiregion/__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/examples/data_solutions/cloudsql-multiregion/fixture/main.tf b/tests/examples/data_solutions/cloudsql-multiregion/fixture/main.tf new file mode 100644 index 00000000..7c709550 --- /dev/null +++ b/tests/examples/data_solutions/cloudsql-multiregion/fixture/main.tf @@ -0,0 +1,27 @@ +/** + * 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. + */ + +module "test" { + source = "../../../../../examples/data-solutions/cloudsql-multiregion/" + data_eng_principals = ["dataeng@example.com"] + postgres_user_password = "my-root-password" + project_id = "project" + project_create = { + billing_account_id = "123456-123456-123456" + parent = "folders/12345678" + } + prefix = "prefix" +} diff --git a/tests/examples/data_solutions/cloudsql-multiregion/test_plan.py b/tests/examples/data_solutions/cloudsql-multiregion/test_plan.py new file mode 100644 index 00000000..1aa11e26 --- /dev/null +++ b/tests/examples/data_solutions/cloudsql-multiregion/test_plan.py @@ -0,0 +1,19 @@ +# 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. + +def test_resources(e2e_plan_runner): + "Test that plan works and the numbers of resources is as expected." + modules, resources = e2e_plan_runner() + assert len(modules) == 11 + assert len(resources) == 49 From 04bc505b70d5c8b503dc7a1b8cd088cb3bb167a0 Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Wed, 13 Apr 2022 16:05:54 +0200 Subject: [PATCH 23/28] Fix roles --- examples/data-solutions/cloudsql-multiregion/main.tf | 5 +++++ .../data_solutions/cloudsql-multiregion/test_plan.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/data-solutions/cloudsql-multiregion/main.tf b/examples/data-solutions/cloudsql-multiregion/main.tf index 9fc7fd6a..4fba8d3b 100644 --- a/examples/data-solutions/cloudsql-multiregion/main.tf +++ b/examples/data-solutions/cloudsql-multiregion/main.tf @@ -36,6 +36,11 @@ locals { local.data_eng_principals_iam, [module.service-account-sql.iam_email] ) + # compute engeneering + "roles/compute.instanceAdmin.v1" = local.data_eng_principals_iam + "roles/compute.osLogin" = local.data_eng_principals_iam + "roles/compute.viewer" = local.data_eng_principals_iam + "roles/iap.tunnelResourceAccessor" = local.data_eng_principals_iam # common roles "roles/logging.admin" = local.data_eng_principals_iam "roles/iam.serviceAccountUser" = concat( diff --git a/tests/examples/data_solutions/cloudsql-multiregion/test_plan.py b/tests/examples/data_solutions/cloudsql-multiregion/test_plan.py index 1aa11e26..e97e22d3 100644 --- a/tests/examples/data_solutions/cloudsql-multiregion/test_plan.py +++ b/tests/examples/data_solutions/cloudsql-multiregion/test_plan.py @@ -16,4 +16,4 @@ def test_resources(e2e_plan_runner): "Test that plan works and the numbers of resources is as expected." modules, resources = e2e_plan_runner() assert len(modules) == 11 - assert len(resources) == 49 + assert len(resources) == 53 From 84e5cbea6804d57dea3480ef6bfd5f21ebf14dc2 Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Wed, 13 Apr 2022 16:39:30 +0200 Subject: [PATCH 24/28] Fix Instance name --- examples/data-solutions/cloudsql-multiregion/cloudsql.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/data-solutions/cloudsql-multiregion/cloudsql.tf b/examples/data-solutions/cloudsql-multiregion/cloudsql.tf index 9e7a5aae..01c30565 100644 --- a/examples/data-solutions/cloudsql-multiregion/cloudsql.tf +++ b/examples/data-solutions/cloudsql-multiregion/cloudsql.tf @@ -18,7 +18,7 @@ module "db" { availability_type = var.sql_configuration.availability_type encryption_key_name = var.cmek_encryption ? module.kms[var.regions.primary].keys.key.id : null network = module.vpc.self_link - name = "${var.prefix}-db-04" + name = "${var.prefix}-db" region = var.regions.primary database_version = var.sql_configuration.database_version tier = var.sql_configuration.tier From cfad0aac9d769602b668c560893ca3d26b4c306c Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Thu, 14 Apr 2022 10:06:04 +0200 Subject: [PATCH 25/28] Remove unused code, update output, fix GCE. --- .../cloudsql-multiregion/gce.tf | 4 +-- .../cloudsql-multiregion/main.tf | 25 ------------------- .../cloudsql-multiregion/outputs.tf | 13 ++++++++++ 3 files changed, 15 insertions(+), 27 deletions(-) diff --git a/examples/data-solutions/cloudsql-multiregion/gce.tf b/examples/data-solutions/cloudsql-multiregion/gce.tf index d0516230..435d0495 100644 --- a/examples/data-solutions/cloudsql-multiregion/gce.tf +++ b/examples/data-solutions/cloudsql-multiregion/gce.tf @@ -50,11 +50,11 @@ module "test-vm" { type = "pd-ssd" size = 10 } - encryption = { + encryption = var.cmek_encryption ? { encrypt_boot = true disk_encryption_key_raw = null kms_key_self_link = var.cmek_encryption ? module.kms[var.regions.primary].keys["key"].id : null - } + } : null metadata = { startup-script = local.startup-script } tags = ["ssh"] } diff --git a/examples/data-solutions/cloudsql-multiregion/main.tf b/examples/data-solutions/cloudsql-multiregion/main.tf index 4fba8d3b..da4e076f 100644 --- a/examples/data-solutions/cloudsql-multiregion/main.tf +++ b/examples/data-solutions/cloudsql-multiregion/main.tf @@ -54,31 +54,6 @@ locals { "serviceAccount:${module.project.service_accounts.robots.sql}" ] } - - # # VPC / Shared VPC variables - # network_subnet_selflink = try( - # module.vpc[0].subnets["${var.region}/subnet"].self_link, - # var.network_config.subnet_self_link - # ) - # shared_vpc_bindings = { - # "roles/compute.networkUser" = [ - # "robot-df", "sa-df-worker" - # ] - # } - # # reassemble in a format suitable for for_each - # shared_vpc_bindings_map = { - # for binding in flatten([ - # for role, members in local.shared_vpc_bindings : [ - # for member in members : { role = role, member = member } - # ] - # ]) : "${binding.role}-${binding.member}" => binding - # } - # shared_vpc_project = try(var.network_config.host_project, null) - # shared_vpc_role_members = { - # robot-df = "serviceAccount:${module.project.service_accounts.robots.dataflow}" - # sa-df-worker = module.service-account-df.iam_email - # } - # use_shared_vpc = var.network_config != null } module "project" { diff --git a/examples/data-solutions/cloudsql-multiregion/outputs.tf b/examples/data-solutions/cloudsql-multiregion/outputs.tf index 50852670..6d6d5178 100644 --- a/examples/data-solutions/cloudsql-multiregion/outputs.tf +++ b/examples/data-solutions/cloudsql-multiregion/outputs.tf @@ -19,6 +19,11 @@ output "connection_names" { value = module.db.connection_names } +output "bucket" { + description = "Cloud storage bucket to import/export data from Cloud SQL." + value = module.gcs.name +} + output "ips" { description = "IP address of each instance." value = module.db.ips @@ -37,3 +42,11 @@ output "demo_commands" { "03_psql" = "psql 'host=127.0.0.1 port=5432 sslmode=disable dbname=${var.postgres_database} user=postgres'" } } + +output "service_accounts" { + description = "Service Accounts." + value = { + "gcs" = module.service-account-gcs.email + "sql" = module.service-account-sql.email + } +} From fc80bc258ab5e6242631ab21fa28cbd70b95d27e Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Thu, 14 Apr 2022 10:09:51 +0200 Subject: [PATCH 26/28] Fix README --- examples/data-solutions/cloudsql-multiregion/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/data-solutions/cloudsql-multiregion/README.md b/examples/data-solutions/cloudsql-multiregion/README.md index 4ad07713..f159dd14 100644 --- a/examples/data-solutions/cloudsql-multiregion/README.md +++ b/examples/data-solutions/cloudsql-multiregion/README.md @@ -77,9 +77,11 @@ You can find computed commands on the Terraform `demo_commands` output. | name | description | sensitive | |---|---|:---:| +| [bucket](outputs.tf#L22) | Cloud storage bucket to import/export data from Cloud SQL. | | | [connection_names](outputs.tf#L17) | Connection name of each instance. | | -| [demo_commands](outputs.tf#L32) | Demo commands. | | -| [ips](outputs.tf#L22) | IP address of each instance. | | -| [project_id](outputs.tf#L27) | ID of the project containing all the instances. | | +| [demo_commands](outputs.tf#L37) | Demo commands. | | +| [ips](outputs.tf#L27) | IP address of each instance. | | +| [project_id](outputs.tf#L32) | ID of the project containing all the instances. | | +| [service_accounts](outputs.tf#L46) | Service Accounts. | | From dea3e73cbd23eea8b648825030452ef18451ab65 Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Thu, 14 Apr 2022 10:58:12 +0200 Subject: [PATCH 27/28] Mention Cloud NAT in the readme --- .../cloudsql-multiregion/README.md | 1 + .../cloudsql-multiregion/diagram.png | Bin 38675 -> 33464 bytes 2 files changed, 1 insertion(+) diff --git a/examples/data-solutions/cloudsql-multiregion/README.md b/examples/data-solutions/cloudsql-multiregion/README.md index f159dd14..ca1598b6 100644 --- a/examples/data-solutions/cloudsql-multiregion/README.md +++ b/examples/data-solutions/cloudsql-multiregion/README.md @@ -9,6 +9,7 @@ The solution will use: - Cloud SQL - Postgre SQL instanced with Private IP - Goocle Cloud Storage bucket to handle database import/export - Google Cloud Engine instance to connect to the Posgre SQL instance +- Google Cloud NAT to access internet resources This is the high level diagram: diff --git a/examples/data-solutions/cloudsql-multiregion/diagram.png b/examples/data-solutions/cloudsql-multiregion/diagram.png index e32543c68729173a5c5f3ef15e6df457863b9b93..35c8ff8e6d0501b1acd87d91e2f14b419f87c632 100644 GIT binary patch literal 33464 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 XEt$gKy>kYiAxMhKiIfU`^83F4={3@+ literal 38675 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 M8%Hq7+UMba0qAIJTmS$7 From be389fde169f03acc72d2bf490c70b6aece80632 Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Thu, 14 Apr 2022 16:04:47 +0200 Subject: [PATCH 28/28] Update READMEs --- examples/README.md | 2 +- examples/data-solutions/README.md | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/README.md b/examples/README.md index ab8a8c79..da15a85c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -5,7 +5,7 @@ This section contains **[foundational examples](./foundations/)** that bootstrap Currently available examples: - **cloud operations** - [Resource tracking and remediation via Cloud Asset feeds](./cloud-operations/asset-inventory-feed-remediation), [Granular Cloud DNS IAM via Service Directory](./cloud-operations/dns-fine-grained-iam), [Granular Cloud DNS IAM for Shared VPC](./cloud-operations/dns-shared-vpc), [Compute Engine quota monitoring](./cloud-operations/quota-monitoring), [Scheduled Cloud Asset Inventory Export to Bigquery](./cloud-operations/scheduled-asset-inventory-export-bq), [Packer image builder](./cloud-operations/packer-image-builder), [On-prem SA key management](./cloud-operations/onprem-sa-key-management), [TCP healthcheck for unmanaged GCE instances](./cloud-operations/unmanaged-instances-healthcheck) -- **data solutions** - [GCE/GCS CMEK via centralized Cloud KMS](./data-solutions/gcs-to-bq-with-least-privileges/), [Cloud Storage to Bigquery with Cloud Dataflow with least privileges](./data-solutions/gcs-to-bq-with-least-privileges/), [Data Platform Foundations](./data-solutions/data-platform-foundations/), [SQL Server AlwaysOn availability groups example](./data-solutions/sqlserver-alwayson) +- **data solutions** - [GCE/GCS CMEK via centralized Cloud KMS](./data-solutions/gcs-to-bq-with-least-privileges/), [Cloud Storage to Bigquery with Cloud Dataflow with least privileges](./data-solutions/gcs-to-bq-with-least-privileges/), [Data Platform Foundations](./data-solutions/data-platform-foundations/), [SQL Server AlwaysOn availability groups example](./data-solutions/sqlserver-alwayson), [Cloud SQL instance with multi-region read replicas](./data-solutions/cloudsql-multiregion/) - **factories** - [The why and the how of resource factories](./factories/README.md) - **foundations** - [single level hierarchy](./foundations/environments/) (environments), [multiple level hierarchy](./foundations/business-units/) (business units + environments) - **networking** - [hub and spoke via peering](./networking/hub-and-spoke-peering/), [hub and spoke via VPN](./networking/hub-and-spoke-vpn/), [DNS and Google Private Access for on-premises](./networking/onprem-google-access-dns/), [Shared VPC with GKE support](./networking/shared-vpc-gke/), [ILB as next hop](./networking/ilb-next-hop), [PSC for on-premises Cloud Function invocation](./networking/private-cloud-function-from-onprem/), [decentralized firewall](./networking/decentralized-firewall) diff --git a/examples/data-solutions/README.md b/examples/data-solutions/README.md index fad34ff0..ec2cfd08 100644 --- a/examples/data-solutions/README.md +++ b/examples/data-solutions/README.md @@ -24,6 +24,12 @@ This [example](./data-platform-foundations/) implements a robust and flexible Da ### SQL Server Always On Availability Groups - + This [example](./data-platform-foundations/) implements SQL Server Always On Availability Groups using Fabric modules. It builds a two node cluster with a fileshare witness instance in an existing VPC and adds the necessary firewalling. The actual setup process (apart from Active Directory operations) has been scripted, so that least amount of manual works needs to performed.
+ +### Cloud SQL instance with multi-region read replicas + + +This [example](./cloudsql-multiregion/) creates a [Cloud SQL instance](https://cloud.google.com/sql) with multi-region read replicas as described in the [Cloud SQL for PostgreSQL disaster recovery](https://cloud.google.com/architecture/cloud-sql-postgres-disaster-recovery-complete-failover-fallback) article. +
\ No newline at end of file