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:
+
+
+
+# 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 000000000..b8ffc12ef
Binary files /dev/null and b/blueprints/data-solutions/composer-2/diagram.png differ
diff --git a/blueprints/data-solutions/composer-2/main.tf b/blueprints/data-solutions/composer-2/main.tf
new file mode 100644
index 000000000..7bb2ea8c3
--- /dev/null
+++ b/blueprints/data-solutions/composer-2/main.tf
@@ -0,0 +1,159 @@
+/**
+ * Copyright 2022 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+locals {
+ iam = {
+ "roles/composer.worker" = [
+ module.comp-sa.iam_email
+ ]
+ "roles/composer.ServiceAgentV2Ext" = [
+ "serviceAccount:${module.project.service_accounts.robots.composer}"
+ ]
+ }
+
+ _shared_vpc_bindings = {
+ "roles/compute.networkUser" = [
+ "prj-cloudservices", "prj-robot-gke"
+ ]
+ "roles/composer.sharedVpcAgent" = [
+ "prj-robot-cs"
+ ]
+ "roles/container.hostServiceAgentUser" = [
+ "prj-robot-gke"
+ ]
+ }
+ shared_vpc_role_members = {
+ prj-cloudservices = "serviceAccount:${module.project.service_accounts.cloud_services}"
+ prj-robot-gke = "serviceAccount:${module.project.service_accounts.robots.container-engine}"
+ prj-robot-cs = "serviceAccount:${module.project.service_accounts.robots.composer}"
+ }
+ # 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)
+ 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