From 5968661e8d45a66eaf3b04cbf7a677f5a4bf41c6 Mon Sep 17 00:00:00 2001 From: lcaggio Date: Mon, 19 Sep 2022 10:34:46 +0200 Subject: [PATCH] First commit --- blueprints/README.md | 2 +- blueprints/data-solutions/README.md | 9 +- .../data-solutions/composer-2/README.md | 106 ++++++++++++ .../composer-2/backend.tf.sample | 30 ++++ .../data-solutions/composer-2/composer.tf | 96 +++++++++++ .../data-solutions/composer-2/diagram.png | Bin 0 -> 17693 bytes blueprints/data-solutions/composer-2/main.tf | 159 ++++++++++++++++++ .../data-solutions/composer-2/outputs.tf | 25 +++ .../data-solutions/composer-2/variables.tf | 85 ++++++++++ .../data_solutions/composer_2/__init__.py | 13 ++ .../data_solutions/composer_2/fixture/main.tf | 27 +++ .../data_solutions/composer_2/test_plan.py | 19 +++ 12 files changed, 569 insertions(+), 2 deletions(-) create mode 100644 blueprints/data-solutions/composer-2/README.md create mode 100644 blueprints/data-solutions/composer-2/backend.tf.sample create mode 100644 blueprints/data-solutions/composer-2/composer.tf create mode 100644 blueprints/data-solutions/composer-2/diagram.png create mode 100644 blueprints/data-solutions/composer-2/main.tf create mode 100644 blueprints/data-solutions/composer-2/outputs.tf create mode 100644 blueprints/data-solutions/composer-2/variables.tf create mode 100644 tests/blueprints/data_solutions/composer_2/__init__.py create mode 100644 tests/blueprints/data_solutions/composer_2/fixture/main.tf create mode 100644 tests/blueprints/data_solutions/composer_2/test_plan.py diff --git a/blueprints/README.md b/blueprints/README.md index b38370bb4..61323dab4 100644 --- a/blueprints/README.md +++ b/blueprints/README.md @@ -5,7 +5,7 @@ This section **[networking blueprints](./networking/)** that implement core patt Currently available blueprints: - **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), [HTTP Load Balancer with Cloud Armor](./cloud-operations/glb_and_armor) -- **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 blueprint](./data-solutions/sqlserver-alwayson), [Cloud SQL instance with multi-region read replicas](./data-solutions/cloudsql-multiregion/) +- **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 blueprint](./data-solutions/sqlserver-alwayson), [Cloud SQL instance with multi-region read replicas](./data-solutions/cloudsql-multiregion/), [Cloud Composer version 2 private instance, supporting Shared VPC and external CMEK key](./data-solutions/composer-2/) - **factories** - [The why and the how of resource factories](./factories/README.md) - **GKE** - [GKE multitenant fleet](./gke/multitenant-fleet/), [Shared VPC with GKE support](./networking/shared-vpc-gke/), [Binary Authorization Pipeline](./gke/binauthz/), [Multi-cluster mesh on GKE (fleet API)](./gke/multi-cluster-mesh-gke-fleet-api/) - **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/blueprints/data-solutions/README.md b/blueprints/data-solutions/README.md index 4abebf9d9..44311b632 100644 --- a/blueprints/data-solutions/README.md +++ b/blueprints/data-solutions/README.md @@ -30,7 +30,7 @@ This [blueprint](./data-platform-foundations/) implements SQL Server Always On A ### Cloud SQL instance with multi-region read replicas - + This [blueprint](./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.
@@ -41,3 +41,10 @@ This [blueprint](./data-playground/) creates a [Vertex AI Notebook](https://cloud.google.com/vertex-ai/docs/workbench/introduction) running on a VPC with a private IP and a dedicated Service Account. A GCS bucket and a BigQuery dataset are created to store inputs and outputs of data experiments.
+ +### Cloud Composer version 2 private instance, supporting Shared VPC and external CMEK key + + +This [blueprint](./composer-2/) creates a [Cloud Composer](https://cloud.google.com/sql) version 2 instance on a VPC with a dedicated service account. The solution supports as inputs: a Shared VPC and Cloud KMS CMEK keys. +
\ No newline at end of file diff --git a/blueprints/data-solutions/composer-2/README.md b/blueprints/data-solutions/composer-2/README.md new file mode 100644 index 000000000..1c05ec6a7 --- /dev/null +++ b/blueprints/data-solutions/composer-2/README.md @@ -0,0 +1,106 @@ +# Cloud Composer version 2 private instance, supporting Shared VPC and external CMEK key + +This blueprint creates a Private instance of [Cloud Composer version 2](https://cloud.google.com/composer/docs/composer-2/composer-versioning-overview) on a VPC with a dedicated service account. + +The solution will use: + - Cloud Composer + - VPC with Private Service Access to deploy resources, if no Shared VPC configuration provided. + - Google Cloud NAT to access internet resources, if no Shared VPC configuration provided. + +The solution supports as inputs: + - Shared VPC + - Cloud KMS CMEK keys + +This is the high level diagram: + +![Cloud Composer 2 architecture overview](./diagram.png "Cloud Composer 2 architecture overview") + +# Requirements +This blueprint will deploy all its resources into the project defined by the project_id variable. Please note that we assume this project already exists. However, if you provide the appropriate values to the `project_create` variable, the project will be created as part of the deployment. + +If `project_create` is left to null, the identity performing the deployment needs the owner role on the project defined by the `project_id` variable. Otherwise, the identity performing the deployment needs `resourcemanager.projectCreator` on the resource hierarchy node specified by `project_create.parent` and `billing.user` on the billing account specified by `project_create.billing_account_id`. + +# Deployment +Run Terraform init: + +``` +$ terraform init +``` + +Configure the Terraform variable in your terraform.tfvars file. You need to spefify at least the following variables: + +``` +project_id = "lcaggioni-sandbox" +prefix = "lc" +``` + +You can run now: + +``` +$ terraform apply +``` + +You can now connect to your instance. + +# Customizations + +## Shared VPC +As is often the case in real-world configurations, this blueprint accepts as input an existing [`Shared-VPC`](https://cloud.google.com/vpc/docs/shared-vpc) via the `network_config` variable. + +Example: +``` +network_config = { + host_project = "PROJECT" + network_self_link = "projects/PROJECT/global/networks/VPC_NAME" + subnet_self_link = "projects/PROJECT/regions/REGION/subnetworks/VPC_NAME" + composer_secondary_ranges = { + pods = "pods" + services = "services" + } +} +``` + +Make sure that: +- The GKE API (`container.googleapis.com`) is enabled in the VPC host project. +- The subnet has secondary ranges configured with 2 ranges: + - pods: `/22` example: `10.10.8.0/22` + - services = `/24` example: 10.10.12.0/24` +- Firewall rules are set, as described in the [documentation](https://cloud.google.com/composer/docs/how-to/managing/configuring-private-ip#step_3_configure_firewall_rules) + +In order to run the example and deploy Cloud Composer on a shared VPC the identity running Terraform must have the following IAM role on the Shared VPC Host project. + - Compute Network Admin (roles/compute.networkAdmin) + - Compute Shared VPC Admin (roles/compute.xpnAdmin) + +## Encryption +As is often the case in real-world configurations, this blueprint accepts as input an existing [`Cloud KMS keys`](https://cloud.google.com/kms/docs/cmek) via the `service_encryption_keys` variable. + +Example: +``` +service_encryption_keys = { + `europe/west1` = `projects/PROJECT/locations/REGION/keyRings/KR_NAME/cryptoKeys/KEY_NAME` +} +``` + + +## Variables + +| name | description | type | required | default | +|---|---|:---:|:---:|:---:| +| [organization_domain](variables.tf#L51) | Organization domain. | string | ✓ | | +| [prefix](variables.tf#L56) | Unique prefix used for resource names. Not used for project if 'project_create' is null. | string | ✓ | | +| [project_id](variables.tf#L70) | Project id, references existing project if `project_create` is null. | string | ✓ | | +| [composer_config](variables.tf#L17) | Composer environemnt configuration. | object({…}) | | {…} | +| [groups](variables.tf#L29) | User groups. | map(string) | | {…} | +| [network_config](variables.tf#L37) | Shared VPC network configurations to use. If null networks will be created in projects with preconfigured values. | object({…}) | | null | +| [project_create](variables.tf#L61) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | | null | +| [region](variables.tf#L75) | Region where instances will be deployed. | string | | "europe-west1" | +| [service_encryption_keys](variables.tf#L81) | Cloud KMS keys to use to encrypt resources. Provide a key for each reagion in use. | map(string) | | null | + +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| [composer_airflow_uri](outputs.tf#L22) | The URI of the Apache Airflow Web UI hosted within the Cloud Composer environment.. | | +| [composer_dag_gcs](outputs.tf#L17) | The Cloud Storage prefix of the DAGs for the Cloud Composer environment. | | + + diff --git a/blueprints/data-solutions/composer-2/backend.tf.sample b/blueprints/data-solutions/composer-2/backend.tf.sample new file mode 100644 index 000000000..49a0883db --- /dev/null +++ b/blueprints/data-solutions/composer-2/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/blueprints/data-solutions/composer-2/composer.tf b/blueprints/data-solutions/composer-2/composer.tf new file mode 100644 index 000000000..28a9fffc1 --- /dev/null +++ b/blueprints/data-solutions/composer-2/composer.tf @@ -0,0 +1,96 @@ +/** + * 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 "comp-sa" { + source = "../../../modules/iam-service-account" + project_id = module.project.project_id + prefix = var.prefix + name = "cmp" + display_name = "Composer service account" + iam = { + "roles/iam.serviceAccountTokenCreator" = [local.groups_iam.data-engineers] + } +} + +resource "google_composer_environment" "env" { + name = "${var.prefix}-composer" + project = module.project.project_id + region = var.region + config { + software_config { + image_version = var.composer_config.image_version + } + workloads_config { + scheduler { + cpu = 0.5 + memory_gb = 1.875 + storage_gb = 1 + count = 1 + } + web_server { + cpu = 0.5 + memory_gb = 1.875 + storage_gb = 1 + } + worker { + cpu = 0.5 + memory_gb = 1.875 + storage_gb = 1 + min_count = 1 + max_count = 3 + } + } + environment_size = var.composer_config.environment_size + + node_config { + network = local.orch_vpc + subnetwork = local.orch_subnet + service_account = module.comp-sa.email + enable_ip_masq_agent = "true" + tags = ["composer-worker"] + ip_allocation_policy { + cluster_secondary_range_name = try( + var.network_config.composer_secondary_ranges.pods, "pods" + ) + services_secondary_range_name = try( + var.network_config.composer_secondary_ranges.services, "services" + ) + } + } + private_environment_config { + enable_private_endpoint = "true" + cloud_sql_ipv4_cidr_block = try( + var.network_config.composer_ip_ranges.cloudsql, "10.20.10.0/24" + ) + master_ipv4_cidr_block = try( + var.network_config.composer_ip_ranges.gke_master, "10.20.11.0/28" + ) + } + dynamic "encryption_config" { + for_each = ( + try(lookup(var.service_encryption_keys, var.region, null) != null, false) + ? { 1 = 1 } + : {} + ) + content { + kms_key_name = try(lookup(var.service_encryption_keys, var.region, null), null) + } + } + } + depends_on = [ + google_project_iam_member.shared_vpc, + ] +} diff --git a/blueprints/data-solutions/composer-2/diagram.png b/blueprints/data-solutions/composer-2/diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..b8ffc12efdd4244a9536379a6e6ff9474c58336c GIT binary patch literal 17693 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%0Aj zg)Bejso~!f@lQfXXH;DS9%u5PJVJ9m<{7F9xB#nIM0~9Ahn5n|FOSKHB@=u(H=Led z3p=k|7m*r5RpipkVKk3z5Za#l69t74|5Z#<{IGX^urxS0I0bQoY;W0?AVolEPW7;< zty46r9&y4I)!P>q^zzwx^YJh1x8=e9N<9bTUN`#9(^XA;=dr~tV>AvgqcIaE+3xIr zXgRhZAOZe99cFBqsWO+nvV0lb<$ZF@MT68;%9DkZBxTXK8$GqIe{&e$+1UwDa#ZZd z+R^n2v)`u#N}jAPdI5D5DNwX?^2^?c^1RL6^&5bTI#K&Z4;|6Mfz zh+!1nA}D=%s=~Ot8qN6T3Z9#ri%upAIORY#wmB6QBXizIgWO)8p1X}(Wm#Dil&Y9g z11(fG;s%N?8LfienWkC3O$@kLC%$W*`W~#3y2KE%Mm`y!edP~T8k~V7!whe+6)Y_n zjRv;%{BAE#cYfV&q~G*kXUxDqb)M@xG-@6z0RD*MG7YYw=w_kR*)UC4I!oV7l zz7a|7EWRUB^IXr_-@ivm*Ntg8TktZ|=it5W*T1R!Gb)c+@iQzYlrkrj(01gx;Zf#o934;5rmyjzQxqbht_Tg^370fwtWj`&w!?M*y%<=zXc9_Hf+QhqYub3>-z6Yt9_K1?5zR+;oh`COjTTcyPL^q7V%q=r#$ z7Uw((J6?|=7emU0N7zI5`cHVt=NDe9mcI~f@ff%*CP;D9UxOOn5O-j9WU4A62>TV9 z3%E)R4^Q~rtogOx-dvJj3-iNFQjX8z9r_vkA+@;f*YL2@+ZrVPw#=9IYpTvx zUj%akE&>WgH-Pv54JD2+&v+a~^J7ceEG0iB%hLU9s_o{4f3Fz|$X@fKUZ~yDr}rg} zPPGIlVSf7`$rIp*%LEgNi-7EUo-4K|VQOkBY3O5JZKcV3(Px2?G4tVT~!|ZBSJ;QUgzywt~oRDq;RLovS&eg?@e%i6@}o- ze>#Fqonw&ou+tN}sS*921kV9=B&zHxYWlT_O6ji*B71Q-_JhEWw;U z7CfUoG=NVNdSKcZY737(Kf=Dg{C&i8^tYKmPaM8cf^}A&D^blJs zKg}{6A;sRwTsu7{)+TkcJBZaq zt8K7tYiqSN*%Qv*dQ}68^hMIV`OC7XO6uvV;XDuRUs*6^54s%NuOq!#a#0SWh?dnQ zhb)c!c1rOL^R8>wC{PIqt(+dDvxvp8_&KR9m;5x)#l_|6hsmq&me?5m!vzhht6=Nd z0jj&;0LT_)5(XtN!cN)461-6O^2q7}X^7;D@fd~uPaU^3*v$NzQUDHWn(y+?zP+!i zhJ{Y`m>l_4 zKsVJz8g|UjHk}<{)Q2VnB#f(J2oY&k{3&v@ z3AX@kezE@ihIj>Xi)N}tYM51reOwgo+kp1jhO(obG=kUHp9M=@F`y*edP&fkx$oBX zD+2=FDO=zJp|)=3ih^Tk)1^?=2)@^JtyR8`2_El2l(%)cr;c<@ zr_bGLIW@KLwK;>`VjW%j!$PA;FNj&d3c~%0D&Rqr0k0eQTIpJ$=}U~aHAbBn)_Gs! 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 binding + } + + shared_vpc_project = try(var.network_config.host_project, null) + use_shared_vpc = var.network_config != null + + vpc_self_link = ( + local.use_shared_vpc + ? var.network_config.network_self_link + : module.vpc.0.self_link + ) + + orch_subnet = ( + local.use_shared_vpc + ? var.network_config.subnet_self_link + : values(module.vpc.0.subnet_self_links)[0] + ) + + orch_vpc = ( + local.use_shared_vpc + ? var.network_config.network_self_link + : module.vpc.0.self_link + ) + groups = { + for k, v in var.groups : k => "${v}@${var.organization_domain}" + } + groups_iam = { + for k, v in local.groups : k => "group:${v}" + } +} + +module "project" { + source = "../../../modules/project" + name = var.project_id + parent = try(var.project_create.parent, null) + billing_account = try(var.project_create.billing_account_id, null) + project_create = var.project_create != null + prefix = var.project_create == null ? null : var.prefix + iam = var.project_create != null ? local.iam : {} + iam_additive = var.project_create == null ? local.iam : {} + services = [ + "cloudkms.googleapis.com", + "container.googleapis.com", + "containerregistry.googleapis.com", + "composer.googleapis.com", + "compute.googleapis.com", + "iap.googleapis.com", + "logging.googleapis.com", + "monitoring.googleapis.com", + "networkmanagement.googleapis.com", + "servicenetworking.googleapis.com", + "storage.googleapis.com", + "storage-component.googleapis.com", + ] + + shared_vpc_service_config = local.shared_vpc_project == null ? null : { + attach = true + host_project = local.shared_vpc_project + service_identity_iam = {} + } + + service_encryption_key_ids = { + composer = [try(lookup(var.service_encryption_keys, var.region, null), null)] + } + + service_config = { + disable_on_destroy = false, disable_dependent_services = false + } +} + +module "vpc" { + source = "../../../modules/net-vpc" + count = local.use_shared_vpc ? 0 : 1 + project_id = module.project.project_id + name = "vpc" + subnets = [ + { + ip_cidr_range = "10.0.0.0/20" + name = "subnet" + region = var.region + secondary_ip_range = { + pods = "10.10.8.0/22" + services = "10.10.12.0/24" + } + } + ] +} + +module "firewall" { + source = "../../../modules/net-vpc-firewall" + count = local.use_shared_vpc ? 0 : 1 + project_id = module.project.project_id + network = module.vpc.0.name + admin_ranges = ["10.0.0.0/20"] +} + +module "nat" { + source = "../../../modules/net-cloudnat" + count = local.use_shared_vpc ? 0 : 1 + project_id = module.project.project_id + region = var.region + name = "${var.prefix}-default" + router_network = module.vpc.0.name +} + +resource "google_project_iam_member" "shared_vpc" { + for_each = local.use_shared_vpc ? local.shared_vpc_bindings_map : {} + project = var.network_config.host_project + role = each.value.role + member = lookup(local.shared_vpc_role_members, each.value.member) +} diff --git a/blueprints/data-solutions/composer-2/outputs.tf b/blueprints/data-solutions/composer-2/outputs.tf new file mode 100644 index 000000000..a2943006e --- /dev/null +++ b/blueprints/data-solutions/composer-2/outputs.tf @@ -0,0 +1,25 @@ +/** + * 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 "composer_dag_gcs" { + description = "The Cloud Storage prefix of the DAGs for the Cloud Composer environment." + value = google_composer_environment.env.config[0].dag_gcs_prefix +} + +output "composer_airflow_uri" { + description = "The URI of the Apache Airflow Web UI hosted within the Cloud Composer environment.." + value = google_composer_environment.env.config[0].airflow_uri +} diff --git a/blueprints/data-solutions/composer-2/variables.tf b/blueprints/data-solutions/composer-2/variables.tf new file mode 100644 index 000000000..36ba2edf1 --- /dev/null +++ b/blueprints/data-solutions/composer-2/variables.tf @@ -0,0 +1,85 @@ +/** + * 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 "composer_config" { + description = "Composer environemnt configuration." + type = object({ + environment_size = string + image_version = string + }) + default = { + environment_size = "ENVIRONMENT_SIZE_SMALL" + image_version = "composer-2-airflow-2" + } +} + +variable "groups" { + description = "User groups." + type = map(string) + default = { + data-engineers = "gcp-data-engineers" + } +} + +variable "network_config" { + description = "Shared VPC network configurations to use. If null networks will be created in projects with preconfigured values." + type = object({ + host_project = string + network_self_link = string + subnet_self_link = string + composer_secondary_ranges = object({ + pods = string + services = string + }) + }) + default = null +} + +variable "organization_domain" { + description = "Organization domain." + type = string +} + +variable "prefix" { + description = "Unique prefix used for resource names. Not used for project if 'project_create' is null." + type = string +} + +variable "project_create" { + description = "Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format." + type = object({ + billing_account_id = string + parent = string + }) + default = null +} + +variable "project_id" { + description = "Project id, references existing project if `project_create` is null." + type = string +} + +variable "region" { + description = "Region where instances will be deployed." + type = string + default = "europe-west1" +} + +variable "service_encryption_keys" { + description = "Cloud KMS keys to use to encrypt resources. Provide a key for each reagion in use." + type = map(string) + default = null +} diff --git a/tests/blueprints/data_solutions/composer_2/__init__.py b/tests/blueprints/data_solutions/composer_2/__init__.py new file mode 100644 index 000000000..6d6d1266c --- /dev/null +++ b/tests/blueprints/data_solutions/composer_2/__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/blueprints/data_solutions/composer_2/fixture/main.tf b/tests/blueprints/data_solutions/composer_2/fixture/main.tf new file mode 100644 index 000000000..384a069a8 --- /dev/null +++ b/tests/blueprints/data_solutions/composer_2/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 = "../../../../../blueprints/data-solutions/composer-2/" + project_id = "project" + + organization_domain = "example.com" + project_create = { + billing_account_id = "123456-123456-123456" + parent = "folders/12345678" + } + prefix = "prefix" +} diff --git a/tests/blueprints/data_solutions/composer_2/test_plan.py b/tests/blueprints/data_solutions/composer_2/test_plan.py new file mode 100644 index 000000000..017d9979f --- /dev/null +++ b/tests/blueprints/data_solutions/composer_2/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) == 5 + assert len(resources) == 28