From 52a182ddbd8ff35f68f269f3ab224ae42224c6db Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Fri, 4 Feb 2022 11:44:55 +0100 Subject: [PATCH 01/18] Switch project module to beta provider for SVPC resources --- modules/project/shared-vpc.tf | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/project/shared-vpc.tf b/modules/project/shared-vpc.tf index 6e720b45..ecce0df5 100644 --- a/modules/project/shared-vpc.tf +++ b/modules/project/shared-vpc.tf @@ -17,11 +17,13 @@ # tfdoc:file:description Shared VPC project-level configuration. resource "google_compute_shared_vpc_host_project" "shared_vpc_host" { - count = try(var.shared_vpc_host_config.enabled, false) ? 1 : 0 - project = local.project.project_id + provider = google-beta + count = try(var.shared_vpc_host_config.enabled, false) ? 1 : 0 + project = local.project.project_id } resource "google_compute_shared_vpc_service_project" "service_projects" { + provider = google-beta for_each = ( try(var.shared_vpc_host_config.enabled, false) ? toset(coalesce(var.shared_vpc_host_config.service_projects, [])) @@ -33,6 +35,7 @@ resource "google_compute_shared_vpc_service_project" "service_projects" { } resource "google_compute_shared_vpc_service_project" "shared_vpc_service" { + provider = google-beta count = try(var.shared_vpc_service_config.attach, false) ? 1 : 0 host_project = var.shared_vpc_service_config.host_project service_project = local.project.project_id From f184ced544a59dbf6dbe014238ad704479f32c85 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Fri, 4 Feb 2022 14:58:37 +0100 Subject: [PATCH 02/18] Add xpnAdmin to diffent folders --- fast/stages/01-resman/branch-networking.tf | 24 ++++++++++++++++++++++ fast/stages/01-resman/branch-teams.tf | 6 ++++++ 2 files changed, 30 insertions(+) diff --git a/fast/stages/01-resman/branch-networking.tf b/fast/stages/01-resman/branch-networking.tf index 3b291197..ce4bc96d 100644 --- a/fast/stages/01-resman/branch-networking.tf +++ b/fast/stages/01-resman/branch-networking.tf @@ -36,6 +36,7 @@ module "branch-network-folder" { "roles/owner" = [module.branch-network-sa.iam_email] "roles/resourcemanager.folderAdmin" = [module.branch-network-sa.iam_email] "roles/resourcemanager.projectCreator" = [module.branch-network-sa.iam_email] + "roles/compute.xpnAdmin" = [module.branch-network-sa.iam_email] } } @@ -57,3 +58,26 @@ module "branch-network-gcs" { "roles/storage.objectAdmin" = [module.branch-network-sa.iam_email] } } + +module "branch-network-prod-folder" { + source = "../../../modules/folder" + parent = "organizations/${var.organization.id}" + name = "prod" + iam = { + "roles/compute.xpnAdmin" = [ + module.branch-teams-prod-projectfactory-sa.iam_email + ] + } +} + + +module "branch-network-dev-folder" { + source = "../../../modules/folder" + parent = "organizations/${var.organization.id}" + name = "dev" + iam = { + "roles/compute.xpnAdmin" = [ + module.branch-teams-dev-projectfactory-sa.iam_email + ] + } +} diff --git a/fast/stages/01-resman/branch-teams.tf b/fast/stages/01-resman/branch-teams.tf index 7967fc9b..408a34ce 100644 --- a/fast/stages/01-resman/branch-teams.tf +++ b/fast/stages/01-resman/branch-teams.tf @@ -94,6 +94,9 @@ module "branch-teams-team-dev-folder" { "roles/resourcemanager.projectCreator" = [ module.branch-teams-dev-projectfactory-sa.iam_email ] + "roles/compute.xpnAdmin" = [ + module.branch-teams-dev-projectfactory-sa.iam_email + ] } } @@ -141,6 +144,9 @@ module "branch-teams-team-prod-folder" { "roles/resourcemanager.projectCreator" = [ module.branch-teams-prod-projectfactory-sa.iam_email ] + "roles/compute.xpnAdmin" = [ + module.branch-teams-prod-projectfactory-sa.iam_email + ] } } From ed4fc477a7f6390e9d97b150a282126f6f764fd0 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Fri, 4 Feb 2022 15:50:05 +0100 Subject: [PATCH 03/18] Update resman outputs and projects --- fast/stages/01-resman/branch-networking.tf | 5 ++--- fast/stages/01-resman/outputs.tf | 6 +++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/fast/stages/01-resman/branch-networking.tf b/fast/stages/01-resman/branch-networking.tf index ce4bc96d..49203051 100644 --- a/fast/stages/01-resman/branch-networking.tf +++ b/fast/stages/01-resman/branch-networking.tf @@ -61,7 +61,7 @@ module "branch-network-gcs" { module "branch-network-prod-folder" { source = "../../../modules/folder" - parent = "organizations/${var.organization.id}" + parent = module.branch-network-folder.id name = "prod" iam = { "roles/compute.xpnAdmin" = [ @@ -70,10 +70,9 @@ module "branch-network-prod-folder" { } } - module "branch-network-dev-folder" { source = "../../../modules/folder" - parent = "organizations/${var.organization.id}" + parent = module.branch-network-folder.id name = "dev" iam = { "roles/compute.xpnAdmin" = [ diff --git a/fast/stages/01-resman/outputs.tf b/fast/stages/01-resman/outputs.tf index 62aed289..d0077fa9 100644 --- a/fast/stages/01-resman/outputs.tf +++ b/fast/stages/01-resman/outputs.tf @@ -53,7 +53,11 @@ locals { } tfvars = { "02-networking" = jsonencode({ - folder_id = module.branch-network-folder.id + folder_ids = { + networking = module.branch-network-folder.id + networking-dev = module.branch-network-dev-folder.id + networking-prod = module.branch-network-prod-folder.id + } project_factory_sa = local._project_factory_sas }) "02-security" = jsonencode({ From 18adf869aee6eb08852399b0a8a77255a38f28c2 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Fri, 4 Feb 2022 16:06:56 +0100 Subject: [PATCH 04/18] Update networking stages to use multiple folders Co-authored-by: Daniel Marzini --- fast/stages/01-resman/README.md | 14 ++++++------- fast/stages/02-networking-nva/README.md | 20 +++++++++---------- fast/stages/02-networking-nva/main.tf | 4 ++-- fast/stages/02-networking-nva/variables.tf | 14 +++---------- fast/stages/02-networking-nva/vpc-landing.tf | 2 +- .../stages/02-networking-nva/vpc-spoke-dev.tf | 2 +- .../02-networking-nva/vpc-spoke-prod.tf | 2 +- fast/stages/02-networking-vpn/README.md | 20 +++++++++---------- fast/stages/02-networking-vpn/main.tf | 4 ++-- fast/stages/02-networking-vpn/variables.tf | 14 +++---------- fast/stages/02-networking-vpn/vpc-landing.tf | 2 +- .../stages/02-networking-vpn/vpc-spoke-dev.tf | 2 +- .../02-networking-vpn/vpc-spoke-prod.tf | 2 +- 13 files changed, 43 insertions(+), 59 deletions(-) diff --git a/fast/stages/01-resman/README.md b/fast/stages/01-resman/README.md index 46a8a383..b83c17a0 100644 --- a/fast/stages/01-resman/README.md +++ b/fast/stages/01-resman/README.md @@ -175,12 +175,12 @@ Due to its simplicity, this stage lends itself easily to customizations: adding | name | description | sensitive | consumers | |---|---|:---:|---| -| [networking](outputs.tf#L84) | Data for the networking stage. | | 02-networking | -| [project_factories](outputs.tf#L94) | Data for the project factories stage. | | xx-teams | -| [providers](outputs.tf#L111) | Terraform provider files for this stage and dependent stages. | ✓ | 02-networking · 02-security · xx-sandbox · xx-teams | -| [sandbox](outputs.tf#L118) | Data for the sandbox stage. | | xx-sandbox | -| [security](outputs.tf#L128) | Data for the networking stage. | | 02-security | -| [teams](outputs.tf#L138) | Data for the teams stage. | | | -| [tfvars](outputs.tf#L151) | Terraform variable files for the following stages. | ✓ | | +| [networking](outputs.tf#L88) | Data for the networking stage. | | 02-networking | +| [project_factories](outputs.tf#L98) | Data for the project factories stage. | | xx-teams | +| [providers](outputs.tf#L115) | Terraform provider files for this stage and dependent stages. | ✓ | 02-networking · 02-security · xx-sandbox · xx-teams | +| [sandbox](outputs.tf#L122) | Data for the sandbox stage. | | xx-sandbox | +| [security](outputs.tf#L132) | Data for the networking stage. | | 02-security | +| [teams](outputs.tf#L142) | Data for the teams stage. | | | +| [tfvars](outputs.tf#L155) | Terraform variable files for the following stages. | ✓ | | diff --git a/fast/stages/02-networking-nva/README.md b/fast/stages/02-networking-nva/README.md index 2f692df5..1b7f232e 100644 --- a/fast/stages/02-networking-nva/README.md +++ b/fast/stages/02-networking-nva/README.md @@ -321,19 +321,19 @@ Don't forget to add a peering zone in the landing project and point it to the ne | name | description | type | required | default | producer | |---|---|:---:|:---:|:---:|:---:| | [billing_account_id](variables.tf#L17) | Billing account id. | string | ✓ | | 00-bootstrap | -| [organization](variables.tf#L99) | Organization details. | object({…}) | ✓ | | 00-bootstrap | -| [prefix](variables.tf#L115) | Prefix used for resources that need unique names. | string | ✓ | | 00-bootstrap | +| [folder_ids](variables.tf#L59) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | map(string) | ✓ | | 01-resman | +| [organization](variables.tf#L91) | Organization details. | object({…}) | ✓ | | 00-bootstrap | +| [prefix](variables.tf#L107) | Prefix used for resources that need unique names. | string | ✓ | | 00-bootstrap | | [custom_adv](variables.tf#L23) | Custom advertisement definitions in name => range format. | map(string) | | {…} | | | [data_dir](variables.tf#L45) | Relative path for the folder storing configuration data for network resources. | string | | "data" | | | [dns](variables.tf#L51) | Onprem DNS resolvers | map(list(string)) | | {…} | | -| [folder_id](variables.tf#L59) | Folder to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | string | | null | 01-resman | -| [l7ilb_subnets](variables.tf#L73) | Subnets used for L7 ILBs. | map(list(object({…}))) | | {…} | | -| [onprem_cidr](variables.tf#L91) | Onprem addresses in name => range format. | map(string) | | {…} | | -| [outputs_location](variables.tf#L109) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string | | null | | -| [project_factory_sa](variables.tf#L121) | IAM emails for project factory service accounts | map(string) | | {} | 01-resman | -| [psa_ranges](variables.tf#L128) | IP ranges used for Private Service Access (e.g. CloudSQL). | map(map(string)) | | {…} | | -| [router_configs](variables.tf#L143) | Configurations for CRs and onprem routers. | map(object({…})) | | {…} | | -| [vpn_onprem_configs](variables.tf#L166) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | | +| [l7ilb_subnets](variables.tf#L65) | Subnets used for L7 ILBs. | map(list(object({…}))) | | {…} | | +| [onprem_cidr](variables.tf#L83) | Onprem addresses in name => range format. | map(string) | | {…} | | +| [outputs_location](variables.tf#L101) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string | | null | | +| [project_factory_sa](variables.tf#L113) | IAM emails for project factory service accounts | map(string) | | {} | 01-resman | +| [psa_ranges](variables.tf#L120) | IP ranges used for Private Service Access (e.g. CloudSQL). | map(map(string)) | | {…} | | +| [router_configs](variables.tf#L135) | Configurations for CRs and onprem routers. | map(object({…})) | | {…} | | +| [vpn_onprem_configs](variables.tf#L158) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | | ## Outputs diff --git a/fast/stages/02-networking-nva/main.tf b/fast/stages/02-networking-nva/main.tf index db03c69a..932191dc 100644 --- a/fast/stages/02-networking-nva/main.tf +++ b/fast/stages/02-networking-nva/main.tf @@ -29,8 +29,8 @@ module "folder" { source = "../../../modules/folder" parent = "organizations/${var.organization.id}" name = "Networking" - folder_create = var.folder_id == null - id = var.folder_id + folder_create = var.folder_ids.networking == null + id = var.folder_ids.networking firewall_policy_factory = { cidr_file = "${var.data_dir}/cidrs.yaml" policy_name = null diff --git a/fast/stages/02-networking-nva/variables.tf b/fast/stages/02-networking-nva/variables.tf index 6756e5b6..355eccf4 100644 --- a/fast/stages/02-networking-nva/variables.tf +++ b/fast/stages/02-networking-nva/variables.tf @@ -56,18 +56,10 @@ variable "dns" { } } -variable "folder_id" { +variable "folder_ids" { # tfdoc:variable:source 01-resman - description = "Folder to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created." - type = string - default = null - validation { - condition = ( - var.folder_id == null || - can(regex("folders/[0-9]{8,}", var.folder_id)) - ) - error_message = "Invalid folder_id. Should be in 'folders/nnnnnnnnnnn' format." - } + description = "Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created." + type = map(string) } variable "l7ilb_subnets" { diff --git a/fast/stages/02-networking-nva/vpc-landing.tf b/fast/stages/02-networking-nva/vpc-landing.tf index 9f6d0a92..e49c6c57 100644 --- a/fast/stages/02-networking-nva/vpc-landing.tf +++ b/fast/stages/02-networking-nva/vpc-landing.tf @@ -20,7 +20,7 @@ module "landing-project" { source = "../../../modules/project" billing_account = var.billing_account_id name = "prod-net-landing-0" - parent = var.folder_id + parent = var.folder_ids.networking prefix = var.prefix service_config = { disable_on_destroy = false diff --git a/fast/stages/02-networking-nva/vpc-spoke-dev.tf b/fast/stages/02-networking-nva/vpc-spoke-dev.tf index 392b5750..628b6490 100644 --- a/fast/stages/02-networking-nva/vpc-spoke-dev.tf +++ b/fast/stages/02-networking-nva/vpc-spoke-dev.tf @@ -20,7 +20,7 @@ module "dev-spoke-project" { source = "../../../modules/project" billing_account = var.billing_account_id name = "dev-net-spoke-0" - parent = var.folder_id + parent = var.folder_ids.networking-dev prefix = var.prefix service_config = { disable_on_destroy = false diff --git a/fast/stages/02-networking-nva/vpc-spoke-prod.tf b/fast/stages/02-networking-nva/vpc-spoke-prod.tf index 320175dc..f0555263 100644 --- a/fast/stages/02-networking-nva/vpc-spoke-prod.tf +++ b/fast/stages/02-networking-nva/vpc-spoke-prod.tf @@ -20,7 +20,7 @@ module "prod-spoke-project" { source = "../../../modules/project" billing_account = var.billing_account_id name = "prod-net-spoke-0" - parent = var.folder_id + parent = var.folder_ids.networking-prod prefix = var.prefix service_config = { disable_on_destroy = false diff --git a/fast/stages/02-networking-vpn/README.md b/fast/stages/02-networking-vpn/README.md index a485d1c7..c32cd2f5 100644 --- a/fast/stages/02-networking-vpn/README.md +++ b/fast/stages/02-networking-vpn/README.md @@ -309,20 +309,20 @@ DNS configurations are centralised in the `dns.tf` file. Spokes delegate DNS res | name | description | type | required | default | producer | |---|---|:---:|:---:|:---:|:---:| | [billing_account_id](variables.tf#L17) | Billing account id. | string | ✓ | | 00-bootstrap | -| [organization](variables.tf#L93) | Organization details. | object({…}) | ✓ | | 00-bootstrap | -| [prefix](variables.tf#L109) | Prefix used for resources that need unique names. | string | ✓ | | 00-bootstrap | +| [folder_idd](variables.tf#L61) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | map(string) | ✓ | | 01-resman | +| [organization](variables.tf#L85) | Organization details. | object({…}) | ✓ | | 00-bootstrap | +| [prefix](variables.tf#L101) | Prefix used for resources that need unique names. | string | ✓ | | 00-bootstrap | | [custom_adv](variables.tf#L23) | Custom advertisement definitions in name => range format. | map(string) | | {…} | | | [custom_roles](variables.tf#L40) | Custom roles defined at the org level, in key => id format. | map(string) | | {} | 00-bootstrap | | [data_dir](variables.tf#L47) | Relative path for the folder storing configuration data for network resources. | string | | "data" | | | [dns](variables.tf#L53) | Onprem DNS resolvers. | map(list(string)) | | {…} | | -| [folder_id](variables.tf#L61) | Folder to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | string | | null | 01-resman | -| [l7ilb_subnets](variables.tf#L75) | Subnets used for L7 ILBs. | map(list(object({…}))) | | {…} | | -| [outputs_location](variables.tf#L103) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string | | null | | -| [project_factory_sa](variables.tf#L115) | IAM emails for project factory service accounts. | map(string) | | {} | 01-resman | -| [psa_ranges](variables.tf#L122) | IP ranges used for Private Service Access (e.g. CloudSQL). | map(map(string)) | | {…} | | -| [router_configs](variables.tf#L137) | Configurations for CRs and onprem routers. | map(object({…})) | | {…} | | -| [vpn_onprem_configs](variables.tf#L161) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | | -| [vpn_spoke_configs](variables.tf#L217) | VPN gateway configuration for spokes. | map(object({…})) | | {…} | | +| [l7ilb_subnets](variables.tf#L67) | Subnets used for L7 ILBs. | map(list(object({…}))) | | {…} | | +| [outputs_location](variables.tf#L95) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string | | null | | +| [project_factory_sa](variables.tf#L107) | IAM emails for project factory service accounts. | map(string) | | {} | 01-resman | +| [psa_ranges](variables.tf#L114) | IP ranges used for Private Service Access (e.g. CloudSQL). | map(map(string)) | | {…} | | +| [router_configs](variables.tf#L129) | Configurations for CRs and onprem routers. | map(object({…})) | | {…} | | +| [vpn_onprem_configs](variables.tf#L153) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | | +| [vpn_spoke_configs](variables.tf#L209) | VPN gateway configuration for spokes. | map(object({…})) | | {…} | | ## Outputs diff --git a/fast/stages/02-networking-vpn/main.tf b/fast/stages/02-networking-vpn/main.tf index 4a3f4748..fcca8867 100644 --- a/fast/stages/02-networking-vpn/main.tf +++ b/fast/stages/02-networking-vpn/main.tf @@ -53,8 +53,8 @@ module "folder" { source = "../../../modules/folder" parent = "organizations/${var.organization.id}" name = "Networking" - folder_create = var.folder_id == null - id = var.folder_id + folder_create = var.folder_ids.networking == null + id = var.folder_ids.networking firewall_policy_factory = { cidr_file = "${var.data_dir}/cidrs.yaml" policy_name = null diff --git a/fast/stages/02-networking-vpn/variables.tf b/fast/stages/02-networking-vpn/variables.tf index 4c134e2f..30af80f1 100644 --- a/fast/stages/02-networking-vpn/variables.tf +++ b/fast/stages/02-networking-vpn/variables.tf @@ -58,18 +58,10 @@ variable "dns" { } } -variable "folder_id" { +variable "folder_idd" { # tfdoc:variable:source 01-resman - description = "Folder to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created." - type = string - default = null - validation { - condition = ( - var.folder_id == null || - can(regex("folders/[0-9]{8,}", var.folder_id)) - ) - error_message = "Invalid folder_id. Should be in 'folders/nnnnnnnnnnn' format." - } + description = "Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created." + type = map(string) } variable "l7ilb_subnets" { diff --git a/fast/stages/02-networking-vpn/vpc-landing.tf b/fast/stages/02-networking-vpn/vpc-landing.tf index 5b6673f7..59505525 100644 --- a/fast/stages/02-networking-vpn/vpc-landing.tf +++ b/fast/stages/02-networking-vpn/vpc-landing.tf @@ -20,7 +20,7 @@ module "landing-project" { source = "../../../modules/project" billing_account = var.billing_account_id name = "prod-net-landing-0" - parent = var.folder_id + parent = var.folder_ids.networking prefix = var.prefix service_config = { disable_on_destroy = false diff --git a/fast/stages/02-networking-vpn/vpc-spoke-dev.tf b/fast/stages/02-networking-vpn/vpc-spoke-dev.tf index 90d11f16..9b3c0f9e 100644 --- a/fast/stages/02-networking-vpn/vpc-spoke-dev.tf +++ b/fast/stages/02-networking-vpn/vpc-spoke-dev.tf @@ -20,7 +20,7 @@ module "dev-spoke-project" { source = "../../../modules/project" billing_account = var.billing_account_id name = "dev-net-spoke-0" - parent = var.folder_id + parent = var.folder_ids.networking-dev prefix = var.prefix service_config = { disable_on_destroy = false diff --git a/fast/stages/02-networking-vpn/vpc-spoke-prod.tf b/fast/stages/02-networking-vpn/vpc-spoke-prod.tf index 0132d8fd..7f42ab2c 100644 --- a/fast/stages/02-networking-vpn/vpc-spoke-prod.tf +++ b/fast/stages/02-networking-vpn/vpc-spoke-prod.tf @@ -20,7 +20,7 @@ module "prod-spoke-project" { source = "../../../modules/project" billing_account = var.billing_account_id name = "prod-net-spoke-0" - parent = var.folder_id + parent = var.folder_ids.networking-prod prefix = var.prefix service_config = { disable_on_destroy = false From 0c0614e12f11f90c904bcd683bb0440c0b754bce Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Fri, 4 Feb 2022 16:43:33 +0100 Subject: [PATCH 05/18] Fix typo in stage2 variable Co-authored-by: Daniel Marzini --- fast/stages/02-networking-vpn/README.md | 2 +- fast/stages/02-networking-vpn/variables.tf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fast/stages/02-networking-vpn/README.md b/fast/stages/02-networking-vpn/README.md index c32cd2f5..e6cb4fe4 100644 --- a/fast/stages/02-networking-vpn/README.md +++ b/fast/stages/02-networking-vpn/README.md @@ -309,7 +309,7 @@ DNS configurations are centralised in the `dns.tf` file. Spokes delegate DNS res | name | description | type | required | default | producer | |---|---|:---:|:---:|:---:|:---:| | [billing_account_id](variables.tf#L17) | Billing account id. | string | ✓ | | 00-bootstrap | -| [folder_idd](variables.tf#L61) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | map(string) | ✓ | | 01-resman | +| [folder_ids](variables.tf#L61) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | map(string) | ✓ | | 01-resman | | [organization](variables.tf#L85) | Organization details. | object({…}) | ✓ | | 00-bootstrap | | [prefix](variables.tf#L101) | Prefix used for resources that need unique names. | string | ✓ | | 00-bootstrap | | [custom_adv](variables.tf#L23) | Custom advertisement definitions in name => range format. | map(string) | | {…} | | diff --git a/fast/stages/02-networking-vpn/variables.tf b/fast/stages/02-networking-vpn/variables.tf index 30af80f1..3eb141e7 100644 --- a/fast/stages/02-networking-vpn/variables.tf +++ b/fast/stages/02-networking-vpn/variables.tf @@ -58,7 +58,7 @@ variable "dns" { } } -variable "folder_idd" { +variable "folder_ids" { # tfdoc:variable:source 01-resman description = "Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created." type = map(string) From 75aff4081fc7eb4c270aa154a70d288ea3f23c42 Mon Sep 17 00:00:00 2001 From: Simone Ruffilli Date: Fri, 4 Feb 2022 19:57:06 +0100 Subject: [PATCH 06/18] default=null for `shared_vpc_self_link ` and `vpc_host_project` (#502) * Update variables.tf * Update README.md --- fast/stages/03-project-factory/prod/README.md | 4 ++-- fast/stages/03-project-factory/prod/variables.tf | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/fast/stages/03-project-factory/prod/README.md b/fast/stages/03-project-factory/prod/README.md index 328571a2..2b31523b 100644 --- a/fast/stages/03-project-factory/prod/README.md +++ b/fast/stages/03-project-factory/prod/README.md @@ -108,11 +108,11 @@ terraform apply | name | description | type | required | default | producer | |---|---|:---:|:---:|:---:|:---:| | [billing_account_id](variables.tf#L19) | Billing account id. | string | ✓ | | 00-bootstrap | -| [shared_vpc_self_link](variables.tf#L44) | Self link for the shared VPC. | string | ✓ | | 02-networking | -| [vpc_host_project](variables.tf#L50) | Host project for the shared VPC. | string | ✓ | | 02-networking | | [data_dir](variables.tf#L25) | Relative path for the folder storing configuration data. | string | | "data/projects" | | | [defaults_file](variables.tf#L38) | Relative path for the file storing the project factory configuration. | string | | "data/defaults.yaml" | | | [environment_dns_zone](variables.tf#L31) | DNS zone suffix for environment. | string | | null | 02-networking | +| [shared_vpc_self_link](variables.tf#L44) | Self link for the shared VPC. | string | | null | 02-networking | +| [vpc_host_project](variables.tf#L51) | Host project for the shared VPC. | string | | null | 02-networking | ## Outputs diff --git a/fast/stages/03-project-factory/prod/variables.tf b/fast/stages/03-project-factory/prod/variables.tf index 8bb9f035..721a0ca9 100644 --- a/fast/stages/03-project-factory/prod/variables.tf +++ b/fast/stages/03-project-factory/prod/variables.tf @@ -45,10 +45,12 @@ variable "shared_vpc_self_link" { # tfdoc:variable:source 02-networking description = "Self link for the shared VPC." type = string + default = null } variable "vpc_host_project" { # tfdoc:variable:source 02-networking description = "Host project for the shared VPC." type = string + default = null } From 7bc10d10390a6f10ed75398872f2a02be614654c Mon Sep 17 00:00:00 2001 From: Antonio Lopez <94461129+ajlopezn@users.noreply.github.com> Date: Sat, 5 Feb 2022 15:54:24 +0100 Subject: [PATCH 07/18] IoT module (#415) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added iot-core module folder * modified iot core readme file * added initial version of the IoT Platform terraform files * añadido detalles en README * Update README.md fix markup issue * Update README.md modified input/output * deleted provider from main and module services * pub_sub topic ids used in the registry instead of creating new ones * variable extra_telemetry_pub_sub_topic_ids modified as default * included data platform integration use case in the doc * devices config moved to yaml file * included example to create device certificates * added HCL and #tftest to README. Moved project/region to variables * solved bug with no optional variables for extra topics or devices yaml * solved bug with no optional variables for extra topics or devices yaml * added example to send MQTT telemetry to IoT Platform * modified doc on Data Platform integration * included diagram * added Registry / Device creation default configuration into variables. Deleted exammple devices yaml file. Corrected module source path * solved comments in PR * applied terraform fmt * solved bug in boolean (bool) variable * updated variables in doc * updated output variable name * updated #tftest:skip on examples as using yaml input makes resources number variable * Update README.md modified input/output * pub_sub topic ids used in the registry instead of creating new ones * variable extra_telemetry_pub_sub_topic_ids modified as default * included data platform integration use case in the doc * devices config moved to yaml file * included example to create device certificates * added HCL and #tftest to README. Moved project/region to variables * solved bug with no optional variables for extra topics or devices yaml * solved bug with no optional variables for extra topics or devices yaml * added example to send MQTT telemetry to IoT Platform * modified doc on Data Platform integration * included diagram * added Registry / Device creation default configuration into variables. Deleted exammple devices yaml file. Corrected module source path * solved comments in PR * applied terraform fmt * solved bug in boolean (bool) variable * updated variables in doc * Updated diagram and documentation for decentralized firewall. * Fixed error with VPC connector. Count was referring to resource attributes that weren't known until after the configuration was applied * Added Cloud Foundation Fabric logos. (#363) * Create README.md * Add logo to readme file * exclusions for audit in locals in logging_sinks.audit-logs now * style fix (whitespaces) * Added dynamic block for dns_cache_config * Fixed boolean variable condition * Adding Apigee Environments as output for apigee-organization module * small var conditional fix * Added GKE pubsub notifications * Default creation false * Updated variable default value * removed extra lines * defaulted to false and conditional output * depend log sinks from IAM roles, fixes #371 * Update README.md * [#374] Add taint when Windows node pools are created to match the one GKE implicitly adds * Update README.md Fixes out of date link on the module usage example * Update README.md - fixed broken link * Added subnet id and self_link to outputs (#377) Adjusted module outputs so that it's easier to consume them when using the factory in a larger codebase. * net-vpc module: added subnet mini-factory * Update README.md * Code cleanup * Adds support for IAM per-subnet IAM bindings via factory * Gracefully handle a null secondary_ip_range * Updated README.md. Added tests. * Added license boilerplate to subnet yaml data in tests * Update README.md * Update README.md * Removed dead code * net-vpc-firewall mini rules-factory * Paying tributes to the linting gods * Fixed small bug on locals * Tests for net-vpc-firewall module * Update CHANGELOG.md * refactor locals (#382) * Add support for partitioned tables on Organization sinks (#380) * Add support for partioned tables on Organization sinks * Update changelog * Fix lint * Fix lint * Use simple bool instead of block * fix README * Fix Readme * Rename variable Co-authored-by: Ludovico Magnocavallo * Improve PSN support in net-vpc module (#384) * improve PSN support * fix variable order * fix example test * fix cloudsql example * Update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md * Remove location from local.prefix * Organization module refactor, in-module firewall policy factory for organization and folder (#385) * move iam and logging to separate files, minimal refactoring * update README * fix example * factory * tfdoc * boilerplate * remove data_folder variable * tfdoc * fix default factory name * add firewall policy to folder module * add factory example * Update CHANGELOG.md * Fix VPC-SC module, add example (#387) * Update CONTRIBUTING.md Suggest change of wording related to `terraform format`: should be done from the top, recursively, and after `pytest` step, because fixtures are also going to be checked for linting during PR. * Add target_node and minor fixes (#389) * Add separate BQ tables per asset type * Add missing BQ location * Add missing CF region * Change Scheduler job to non-generic * Add target_node, pass tests * Do tfdoc.py * Do terraform fmt * Do terraform fmt -recursive for tests' fixtures * Fix typo in test's variables * Added support for Stateful Managed Instance Groups (#367) * First iteration updates * All tests passing * Updated README and var descriptions * Updated README * Updated example README * Consolidated stateful vars * consolidated stateful vars * Updated README * Requested changes to try * Fixed README examples and try Co-authored-by: Ludovico Magnocavallo * SA key uploading and credentials json generation with terraform. * Fix typo in the SA module readme * TF fmt * Update iam-sa docs * Finalize onprem-sa-ket-mgmt example * Add basic test for onprem-sa-mgmt example * Refactoring * Reorder variables * Rewording, fix typos * Bump tftest version Bump tftest version to 1.6.1 and ensure test runners can update providers if needed and available. * Add bq_table_overwrite handling * Add optional BQ table as a file export config * Add getattr() to retrieve attribute from format * Add roles/bigquery.jobUser for CF * Update test_plan and README * Change README, add diagram ( gcpdraw a6e4ec4f-7d6c-4796-b63e-ee4ce7b1792f ) * Change README to two diagrams * Add count to toggle optional modules * Change README * Resolve order of variables test * Linux sysctls configuration and Kubelet config (#388) * Linux sysctls configuration and Kubelet config * Fix terraform linting issues * Updated README.md * Updated Kubelet config object type * Update readme Co-authored-by: Sergio Tejón Co-authored-by: Julio Castillo * net-address: Added support for PSA ranges * Update outputs.tf * Fixes typo * Resolved merge conflict * Update CHANGELOG.md * Add metric scopes support (#397) * Add metric scopes support * Fix typo * new tfdoc block in README Co-authored-by: Ludovico Magnocavallo * fix tfdoc replacement (#398) * Update CHANGELOG.md * Ludo hfw fixes (#400) * fix tfdoc replacement * ignore changes to rule description * add folder example, fixes #339 * Update CHANGELOG.md * Fix ignore changes for hfw rules (#401) * fix tfdoc replacement * ignore changes to rule description * add folder example, fixes #339 * fix ignore changes in org and folder module * Update CHANGELOG.md * update documentation check * backport tfdoc and check doc fixes * Re-run pydoc * Redo pydoc + local linting checks * update tfdoc (#404) * Org/Folder: Allow for policy association when using rule factory (#405) * Org/Folder: Allow for policy association when using rule factory * Fix linting issue * Update hierarchical firewall resource This replaces all the `google_compute_organization_security_*` resources with the newer `google_compute_firewall_*` resources. * Fix typo. * Update CHANGELOG.md * Typo fix * New vpc-sc module implementation (#406) * first implementation * minimal output * split service perimeters in regular and bridge * tests and fixes * new vpc-sc implementation * remove providers file used for testing * remove provider used during development * Update CHANGELOG.md * Update CHANGELOG.md * Update README.md * fix cases where bridge perimeter status resources are null (#408) * Update CHANGELOG.md * Copyright bump (#410) * remove lifecycle block from vpc sc perimeters (#412) * Update CHANGELOG.md * Update CHANGELOG.md * Interpolate access levels by key in vpc sc module (#414) * interpolate access levels by key * fix access level reference * fix num resources in last README test * Update CHANGELOG.md * fix ingress policies in vpc sc module * disable device policy support in vpc-sc module * depend bridge from regular perimeters in vpc sc module * Update CONTRIBUTING.md * add versions to vpc-sc module * Update CONTRIBUTING.md * updated output variable name * updated #tftest:skip on examples as using yaml input makes resources number variable * changed pub_sub to pubsub * unified protocol input variable * changed extra_telemetry_pubsub_topic_ids variable type to: map * solved issue in README.md * grouped device configuration variables * device config is now done individually per each device in the yaml file * changes on device config yaml processing: 1 single yaml directory, use try() while parsing, example identation corrected * updated headers to 2022 * removed get-pip.py --- modules/iot-core/README.md | 145 +++++++++++++++++++++++++++++++ modules/iot-core/diagram.png | Bin 0 -> 481113 bytes modules/iot-core/diagram_iot.png | Bin 0 -> 53046 bytes modules/iot-core/main.tf | 95 ++++++++++++++++++++ modules/iot-core/outputs.tf | 20 +++++ modules/iot-core/variables.tf | 67 ++++++++++++++ 6 files changed, 327 insertions(+) create mode 100644 modules/iot-core/README.md create mode 100644 modules/iot-core/diagram.png create mode 100644 modules/iot-core/diagram_iot.png create mode 100644 modules/iot-core/main.tf create mode 100644 modules/iot-core/outputs.tf create mode 100644 modules/iot-core/variables.tf diff --git a/modules/iot-core/README.md b/modules/iot-core/README.md new file mode 100644 index 00000000..d28006db --- /dev/null +++ b/modules/iot-core/README.md @@ -0,0 +1,145 @@ +# Google Cloud IoT Core Module + +This module sets up Cloud IoT Core Registry, registers IoT Devices and configures Pub/Sub topics required in Cloud IoT Core. + +To use this module, ensure the following APIs are enabled: +* pubsub.googleapis.com +* cloudiot.googleapis.com + +## Simple Example + +Basic example showing how to create an IoT Platform (IoT Core), connected to a set of given Pub/Sub topics and provision IoT devices. + +Devices certificates must exist before calling this module. You can generate these certificates using the following command + +``` +openssl req -x509 -newkey rsa:2048 -keyout rsa_private.pem -nodes -out rsa_cert.pem -subj "/CN=unused" +``` + +And then provision public certificate path, together with the rest of device configuration in a devices yaml file following the following format +```yaml +device_id: # id of your IoT Device + is_blocked: # false to allow device connection with IoT Registry + is_gateway: # true to indicate the device connecting acts as a gateway for other IoT Devices + log_level: # device logs level + certificate_file: # public certificate path, generated as explained in the previous step + certificate_format: # Certificates format values are RSA_PEM, RSA_X509_PEM, ES256_PEM, and ES256_X509_PEM +``` + +Example Device config yaml configuration +```yaml +device_1: + is_blocked: false + is_gateway: false + log_level: INFO + certificate_file: device_certs/rsa_cert5.pem + certificate_format: RSA_X509_PEM +device_2: + is_blocked: true + is_gateway: false + log_level: INFO + certificate_file: device_certs/rsa_cert5.pem + certificate_format: RSA_X509_PEM +``` + +```hcl +module "iot-platform" { + source = "./modules/iot-core" + project_id = "my_project_id" + region = "europe-west1" + telemetry_pubsub_topic_id = "telemetry_topic_id" + status_pubsub_topic_id = "status_topic_id" + protocols = { + http = false, + mqtt = true + } + devices_config_directory = "./devices_config_folder" +} +# tftest:skip + +``` + +Now, we can test sending telemetry messages from devices to our IoT Platform, for example using the MQTT demo client at https://github.com/googleapis/nodejs-iot/tree/main/samples/mqtt_example + +## Example with specific PubSub topics for custom MQTT topics + +If you need to match specific MQTT topics (eg, /temperature) into specific PubSub topics, you can use extra_telemetry_pubsub_topic_ids for that, as in the following example: + +```hcl +module "iot-platform" { + source = "./modules/iot-core" + project_id = "my_project_id" + region = "europe-west1" + telemetry_pubsub_topic_id = "telemetry_topic_id" + status_pubsub_topic_id = "status_topic_id" + extra_telemetry_pubsub_topic_ids = { + "temperature" = "temp_topic_id", + "humidity" = "hum_topic_id" + } + protocols = { + http = false, + mqtt = true + } + devices_config_directory = "./devices_config_folder" +} +# tftest:skip + +``` + +## Example integrated with Data Foundation Platform +In this example, we will show how to extend the **[Data Foundations Platform](../../data-solutions/data-platform-foundations/)** to include IoT Platform as a new source of data. + +![Target architecture](./diagram_iot.png) + +1. First, we will setup Environment following instructions in **[Environment Setup](../../data-solutions/data-platform-foundations/01-environment/)** to setup projects and SAs required. Get output variable project_ids.landing as will be used later + +1. Second, execute instructions in **[Environment Setup](../../data-solutions/data-platform-foundations/02-resources/)** to provision PubSub, DataFlow, BQ,... Get variable landing-pubsub as will be used later to create IoT Registry + +1. Now it is time to provision IoT Platform. Modify landing-project-id and landing_pubsub_topic_id with output variables obtained before. Create device certificates as shown in the Simple Example and register them in devices.yaml file together with deviceids. + +```hcl +module "iot-platform" { + source = "./modules/iot-core" + project_id = "landing-project-id" + region = "europe-west1" + telemetry_pubsub_topic_id = "landing_pubsub_topic_id" + status_pubsub_topic_id = "status_pubsub_topic_id" + protocols = { + http = false, + mqtt = true + } + devices_config_directory = "./devices_config_folder" +} +# tftest:skip +``` +1. After that, we can setup the pipeline "PubSub to BigQuery" shown at **[Pipeline Setup](../../data-solutions/data-platform-foundations/03-pipeline/pubsub_to_bigquery.md)** + +1. Finally, instead of testing the pipeline by sending messages to PubSub, we can now test sending telemetry messages from simulated IoT devices to our IoT Platform, for example using the MQTT demo client at https://github.com/googleapis/nodejs-iot/tree/main/samples/mqtt_example . We shall edit the client script cloudiot_mqtt_example_nodejs.js to send messages following the pipeline message format, so they are processed by DataFlow job and inserted in the BigQuery table. +``` +const payload = '{"name": "device4", "surname": "NA", "timestamp":"'+Math.floor(Date.now()/1000)+'"}'; +``` + +Or even better, create a new BigQuery table with our IoT sensors data columns and modify the DataFlow job to push data to it. + + +## Variables + +| name | description | type | required | default | +|---|---|:---:|:---:|:---:| +| [devices_config_directory](variables.tf#L17) | Path to folder where devices configs are stored in yaml format. Folder may include subfolders with configuration files. Files suffix must be `.yaml`. | string | ✓ | | +| [project_id](variables.tf#L34) | Project were resources will be deployed | string | ✓ | | +| [region](variables.tf#L48) | Region were resources will be deployed | string | ✓ | | +| [status_pubsub_topic_id](variables.tf#L59) | pub sub topic for status messages (GCP-->Device) | string | ✓ | | +| [telemetry_pubsub_topic_id](variables.tf#L64) | pub sub topic for telemetry messages (Device-->GCP) | string | ✓ | | +| [extra_telemetry_pubsub_topic_ids](variables.tf#L22) | additional pubsub topics linked to adhoc MQTT topics (Device-->GCP) in the format MQTT_TOPIC: PUBSUB_TOPIC_ID | map(string) | | {} | +| [log_level](variables.tf#L28) | IoT Registry Log level | string | | "INFO" | +| [protocols](variables.tf#L39) | IoT protocols (HTTP / MQTT) activation | object({…}) | | { http = true, mqtt = true } | +| [registry_name](variables.tf#L53) | Name for the IoT Core Registry | string | | "cloudiot-registry" | + +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| [iot_registry](outputs.tf#L17) | Cloud IoT Core Registry | | + + diff --git a/modules/iot-core/diagram.png b/modules/iot-core/diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..7a3393212d03a5331d898729b29fc87ec7d8d24a GIT binary patch literal 481113 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