diff --git a/fast/project-templates/.gitignore b/fast/project-templates/.gitignore
index 3073d501b..cc8997e22 100644
--- a/fast/project-templates/.gitignore
+++ b/fast/project-templates/.gitignore
@@ -1 +1,2 @@
**/*-providers.tf
+**/*.tfvars
diff --git a/fast/project-templates/data-mongodb/README.md b/fast/project-templates/data-mongodb/README.md
new file mode 100644
index 000000000..a44ae55dc
--- /dev/null
+++ b/fast/project-templates/data-mongodb/README.md
@@ -0,0 +1,90 @@
+# MongoDB Atlas
+
+This simple setup allows creating and configuring a managed [MongoDB Atlas](https://cloud.google.com/mongodb) cluster, and connecting it to a local VPC network via Private Endpoints.
+
+## Prerequisites
+
+The [`project.yaml`](./project.yaml) file describes the project-level configuration needed in terms of API activation and IAM bindings.
+
+If you are deploying this inside a FAST-enabled organization, the file can be lightly edited to match your configuration, and then used directly in the [project factory](../../stages/2-project-factory/).
+
+This Terraform can of course be deployed using any pre-existing project. In that case use the YAML file to determine the configuration you need to set on the project:
+
+- enable the APIs listed under `services`
+- grant the permissions listed under `iam` to the principal running Terraform, either machine (service account) or human
+
+## Variable Configuration
+
+Configuration is mostly done via the `atlas_config` and `vpc_config` variables. Note that:
+
+- VPC configuration can be set to reference a Shared VPC Host network like shown below, or an in-project network if that is preferred
+- the PSC CIDR block is used to allocate the required 50 endpoint addresses in the VPC, so it needs to be large enough to accommodate them
+- the Atlas region must match the GCP subnetwork region
+
+Bringing up a cluster and the associated connectivity from scratch will require approximately 30 minutes.
+
+```hcl
+atlas_config = {
+ cluster_name = "test-0"
+ organization_id = "fmoajt0b2fwdvp9yvu7m7zl2"
+ project_name = "my-atlas-project"
+ region = "NORTH_AMERICA_NORTHEAST_1"
+ database_version = "7.0"
+ instance_size = "M10"
+ provider = {
+ public_key = "xxxx"
+ private_key = "xxxxx-xxxx-xxxx-xxxx-xxxxxxxx"
+ }
+}
+project_id = "my-prod-shared-mongodb-0"
+vpc_config = {
+ network_name = "dev-spoke-0"
+ subnetwork_id = "projects/my-dev-net-spoke-0/regions/northamerica-northeast1/subnetworks/gce"
+ psc_cidr_block = "10.8.11.192/26"
+}
+# tftest skip
+```
+
+## Variables
+
+| name | description | type | required | default |
+|---|---|:---:|:---:|:---:|
+| [atlas_config](variables.tf#L17) | MongoDB Atlas configuration. | object({…}) | ✓ | |
+| [project_id](variables.tf#L40) | Project id where the registries will be created. | string | ✓ | |
+| [vpc_config](variables.tf#L45) | VPC configuration. | object({…}) | ✓ | |
+| [name](variables.tf#L33) | Prefix used for all resource names. | string | | "mongodb" |
+
+## Outputs
+
+| name | description | sensitive |
+|---|---|:---:|
+| [atlas_cluster](outputs.tf#L17) | MongoDB Atlas cluster. | |
+| [atlas_project](outputs.tf#L31) | MongoDB Atlas project. | |
+| [endpoints](outputs.tf#L40) | MongoDB Atlas endpoints. | |
+
+## Test
+
+```hcl
+module "test" {
+ source = "./fabric/fast/project-templates/data-mongodb"
+ atlas_config = {
+ cluster_name = "test-0"
+ organization_id = "fmoajt0b2fwdvp9yvu7m7zl2"
+ project_name = "my-atlas-project"
+ region = "NORTH_AMERICA_NORTHEAST_1"
+ database_version = "7.0"
+ instance_size = "M10"
+ provider = {
+ public_key = "xxxx"
+ private_key = "xxxxx-xxxx-xxxx-xxxx-xxxxxxxx"
+ }
+ }
+ project_id = "my-prod-shared-mongodb-0"
+ vpc_config = {
+ network_name = "dev-spoke-0"
+ subnetwork_id = "projects/my-dev-net-spoke-0/regions/northamerica-northeast1/subnetworks/gce"
+ psc_cidr_block = "10.8.11.192/26"
+ }
+}
+# tftest modules=2 resources=104
+```
diff --git a/fast/project-templates/data-mongodb/main.tf b/fast/project-templates/data-mongodb/main.tf
new file mode 100644
index 000000000..ed05bb83d
--- /dev/null
+++ b/fast/project-templates/data-mongodb/main.tf
@@ -0,0 +1,75 @@
+/**
+ * Copyright 2025 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 {
+ region = regex(
+ "projects/[^/]+/regions/([^/]+)/subnetworks/[^/]+$",
+ var.vpc_config.subnetwork_id
+ )[0]
+}
+
+module "addresses" {
+ source = "../../../modules/net-address"
+ project_id = var.project_id
+ psc_addresses = {
+ for i in range(50) : "${var.name}-${i}" => {
+ address = cidrhost(var.vpc_config.psc_cidr_block, i)
+ region = local.region
+ subnet_self_link = var.vpc_config.subnetwork_id
+ service_attachment = {
+ psc_service_attachment_link = (
+ mongodbatlas_privatelink_endpoint.default.service_attachment_names[i]
+ )
+ global_access = true
+ }
+ }
+ }
+}
+
+resource "mongodbatlas_project" "default" {
+ name = var.atlas_config.project_name
+ org_id = var.atlas_config.organization_id
+}
+
+resource "mongodbatlas_cluster" "default" {
+ project_id = mongodbatlas_project.default.id
+ name = var.atlas_config.cluster_name
+ provider_name = "GCP"
+ provider_instance_size_name = var.atlas_config.instance_size
+ provider_region_name = var.atlas_config.region
+ mongo_db_major_version = var.atlas_config.database_version
+}
+
+resource "mongodbatlas_privatelink_endpoint" "default" {
+ project_id = mongodbatlas_project.default.id
+ provider_name = "GCP"
+ region = var.atlas_config.region
+}
+
+resource "mongodbatlas_privatelink_endpoint_service" "default" {
+ project_id = mongodbatlas_privatelink_endpoint.default.project_id
+ private_link_id = mongodbatlas_privatelink_endpoint.default.private_link_id
+ provider_name = "GCP"
+ endpoint_service_id = var.vpc_config.network_name
+ gcp_project_id = var.project_id
+ dynamic "endpoints" {
+ for_each = module.addresses.psc
+ content {
+ ip_address = endpoints.value.address.address
+ endpoint_name = endpoints.value.forwarding_rule.name
+ }
+ }
+}
diff --git a/fast/project-templates/data-mongodb/outputs.tf b/fast/project-templates/data-mongodb/outputs.tf
new file mode 100644
index 000000000..6efd33e78
--- /dev/null
+++ b/fast/project-templates/data-mongodb/outputs.tf
@@ -0,0 +1,46 @@
+/**
+ * Copyright 2025 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 "atlas_cluster" {
+ description = "MongoDB Atlas cluster."
+ value = {
+ id = mongodbatlas_cluster.default.cluster_id
+ mongo_uri = mongodbatlas_cluster.default.mongo_uri
+ mongo_uri_with_options = mongodbatlas_cluster.default.mongo_uri_with_options
+ name = mongodbatlas_cluster.default.name
+ project_id = mongodbatlas_cluster.default.project_id
+ region = mongodbatlas_cluster.default.provider_region_name
+ size = mongodbatlas_cluster.default.provider_instance_size_name
+ srv_address = mongodbatlas_cluster.default.srv_address
+ }
+}
+
+output "atlas_project" {
+ description = "MongoDB Atlas project."
+ value = {
+ id = mongodbatlas_project.default.id
+ name = mongodbatlas_project.default.name
+ org_id = mongodbatlas_project.default.org_id
+ }
+}
+
+output "endpoints" {
+ description = "MongoDB Atlas endpoints."
+ value = sort([
+ for v in mongodbatlas_privatelink_endpoint_service.default.endpoints :
+ v.ip_address
+ ])
+}
diff --git a/fast/project-templates/data-mongodb/project.yaml b/fast/project-templates/data-mongodb/project.yaml
new file mode 100644
index 000000000..3c7a6e073
--- /dev/null
+++ b/fast/project-templates/data-mongodb/project.yaml
@@ -0,0 +1,47 @@
+# Copyright 2025 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.
+
+# yaml-language-server: $schema=../../stages/2-project-factory/schemas/project.schema.json
+
+# edit parent to suit desired folder where project is created
+parent: shared
+name: prod-shared-mongodb-0
+services:
+ - compute.googleapis.com
+ - logging.googleapis.com
+ - monitoring.googleapis.com
+# if automation resources are not used, grant these roles to the principal
+# that will be used to apply this Terraform setup
+iam:
+ roles/compute.admin:
+ - automation/rw
+ roles/servicedirectory.admin:
+ - automation/rw
+automation:
+ project: foo-prod-shared-iac-0
+ service_accounts:
+ rw:
+ description: Read/write automation service account for MongoDB.
+ bucket:
+ description: Terraform state bucket for MongoDB.
+ iam:
+ roles/storage.objectAdmin:
+ - automation/rw
+ roles/storage.objectViewer:
+ - automation/rw
+# edit or comment shared VPC service host
+shared_vpc_service_config:
+ host_project: dev-spoke-0
+ network_users:
+ - automation/rw
diff --git a/fast/project-templates/data-mongodb/providers_override.tf b/fast/project-templates/data-mongodb/providers_override.tf
new file mode 100644
index 000000000..6d2ad97b6
--- /dev/null
+++ b/fast/project-templates/data-mongodb/providers_override.tf
@@ -0,0 +1,29 @@
+/**
+ * Copyright 2025 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.
+ */
+
+terraform {
+ required_providers {
+ mongodbatlas = {
+ source = "mongodb/mongodbatlas"
+ version = "~> 1.0"
+ }
+ }
+}
+
+provider "mongodbatlas" {
+ public_key = var.atlas_config.provider.public_key
+ private_key = var.atlas_config.provider.private_key
+}
diff --git a/fast/project-templates/data-mongodb/variables.tf b/fast/project-templates/data-mongodb/variables.tf
new file mode 100644
index 000000000..d2cf44428
--- /dev/null
+++ b/fast/project-templates/data-mongodb/variables.tf
@@ -0,0 +1,52 @@
+/**
+ * Copyright 2025 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 "atlas_config" {
+ description = "MongoDB Atlas configuration."
+ type = object({
+ cluster_name = string
+ organization_id = string
+ project_name = string
+ region = string
+ database_version = optional(string)
+ instance_size = optional(string)
+ provider = object({
+ private_key = string
+ public_key = string
+ })
+ })
+}
+
+variable "name" {
+ description = "Prefix used for all resource names."
+ type = string
+ nullable = true
+ default = "mongodb"
+}
+
+variable "project_id" {
+ description = "Project id where the registries will be created."
+ type = string
+}
+
+variable "vpc_config" {
+ description = "VPC configuration."
+ type = object({
+ psc_cidr_block = string
+ network_name = string
+ subnetwork_id = string
+ })
+}
diff --git a/fast/project-templates/os-apt-registries/project.yaml b/fast/project-templates/os-apt-registries/project.yaml
index 8a1e991d8..649fa8a08 100644
--- a/fast/project-templates/os-apt-registries/project.yaml
+++ b/fast/project-templates/os-apt-registries/project.yaml
@@ -17,30 +17,29 @@
# TODO: edit and uncomment the following line to create the project in a folder
# parent: shared
-name: os-apt-0
+name: prod-os-apt-0
services:
- accesscontextmanager.googleapis.com
- artifactregistry.googleapis.com
automation:
# TODO: edit the automation project and optionally edit resource names
- project: pf-automation-0
+ project: prod-pf-iac-0
service_accounts:
rw:
description: Read/write automation service account for apt registries.
- buckets:
- tf-state:
- description: Terraform state bucket for apt registries.
- iam:
- roles/storage.objectCreator:
- - rw
- roles/storage.objectViewer:
- - rw
+ bucket:
+ description: Terraform state bucket for apt registries.
+ iam:
+ roles/storage.objectCreator:
+ - rw
+ roles/storage.objectViewer:
+ - rw
iam:
roles/viewer:
- - rw
+ - prod-os-apt-0/rw
roles/artifactregistry.admin:
- - rw
+ - prod-os-apt-0/rw
# TODO: add instance service accounts that need access to the registries
# roles/artifactregistry.writer:
# - serviceAccount:foo@bar
\ No newline at end of file
diff --git a/modules/net-address/README.md b/modules/net-address/README.md
index 677e582f9..29938f777 100644
--- a/modules/net-address/README.md
+++ b/modules/net-address/README.md
@@ -249,7 +249,8 @@ module "addresses" {
| [ipsec_interconnect_addresses](outputs.tf#L41) | Allocated internal addresses for HA VPN over Cloud Interconnect. | |
| [network_attachment_ids](outputs.tf#L49) | IDs of network attachments. | |
| [psa_addresses](outputs.tf#L57) | Allocated internal addresses for PSA endpoints. | |
-| [psc_addresses](outputs.tf#L65) | Allocated internal addresses for PSC endpoints. | |
+| [psc](outputs.tf#L65) | Allocated resources for PSC endpoints. | |
+| [psc_addresses](outputs.tf#L99) | Allocated internal addresses for PSC endpoints. | |
## Fixtures
diff --git a/modules/net-address/outputs.tf b/modules/net-address/outputs.tf
index 95ab38b11..5e117ff62 100644
--- a/modules/net-address/outputs.tf
+++ b/modules/net-address/outputs.tf
@@ -62,6 +62,40 @@ output "psa_addresses" {
}
}
+output "psc" {
+ description = "Allocated resources for PSC endpoints."
+ value = merge(
+ {
+ for k, v in local.global_psc :
+ k => {
+ address = {
+ address = google_compute_global_address.psc[k].address
+ id = google_compute_global_address.psc[k].id
+ name = google_compute_global_address.psc[k].name
+ }
+ forwarding_rule = {
+ id = try(google_compute_global_forwarding_rule.psc_consumer[k].id, null)
+ name = try(google_compute_global_forwarding_rule.psc_consumer[k].name, null)
+ }
+ }
+ },
+ {
+ for k, v in local.regional_psc :
+ k => {
+ address = {
+ address = google_compute_address.psc[k].address
+ id = google_compute_address.psc[k].id
+ name = google_compute_address.psc[k].name
+ }
+ forwarding_rule = {
+ id = try(google_compute_forwarding_rule.psc_consumer[k].id, null)
+ name = try(google_compute_forwarding_rule.psc_consumer[k].name, null)
+ }
+ }
+ }
+ )
+}
+
output "psc_addresses" {
description = "Allocated internal addresses for PSC endpoints."
value = merge(
diff --git a/modules/net-vpc/README.md b/modules/net-vpc/README.md
index 8913dea56..62d298d69 100644
--- a/modules/net-vpc/README.md
+++ b/modules/net-vpc/README.md
@@ -105,7 +105,7 @@ module "vpc" {
}
]
}
-# tftest modules=1 resources=8 inventory=subnet-options.yaml e2e
+# tftest modules=1 resources=9 inventory=subnet-options.yaml e2e
```
### Subnet IAM
diff --git a/tools/lockfile/default-versions_override.tf b/tools/lockfile/default-versions_override.tf
index 0cf335251..f9822ecdc 100644
--- a/tools/lockfile/default-versions_override.tf
+++ b/tools/lockfile/default-versions_override.tf
@@ -23,5 +23,9 @@ terraform {
source = "integrations/github"
version = "~> 5.0"
}
+ mongodbatlas = {
+ source = "mongodb/mongodbatlas"
+ version = "~> 1.0"
+ }
}
}