diff --git a/fast/stages/2-networking/README.md b/fast/stages/2-networking/README.md index 0ea3bbeb5..7e9d98b79 100644 --- a/fast/stages/2-networking/README.md +++ b/fast/stages/2-networking/README.md @@ -367,7 +367,7 @@ Internally created resources are mapped to context namespaces, and use specific | [factory-peering.tf](./factory-peering.tf) | VPC Peering factory. | | google_compute_network_peering | | [factory-projects.tf](./factory-projects.tf) | Projects factory. | project-factory | | | [factory-routers.tf](./factory-routers.tf) | Routers factory. | | google_compute_router | -| [factory-vlan-attachments.tf](./factory-vlan-attachments.tf) | VLAN attachments factory. | net-vlan-attachment | | +| [factory-vlan-attachments.tf](./factory-vlan-attachments.tf) | VLAN attachments factory. | net-vlan-attachment | google_compute_interconnect_attachment_group | | [factory-vpcs.tf](./factory-vpcs.tf) | VPC and firewall rules factory. | net-vpc · net-vpc-factory | | | [factory-vpns.tf](./factory-vpns.tf) | VPNs factory. | net-vpn-ha | google_compute_ha_vpn_gateway | | [main.tf](./main.tf) | Module-level locals and resources. | | | diff --git a/fast/stages/2-networking/factory-vlan-attachments.tf b/fast/stages/2-networking/factory-vlan-attachments.tf index d29198a94..7174823f7 100644 --- a/fast/stages/2-networking/factory-vlan-attachments.tf +++ b/fast/stages/2-networking/factory-vlan-attachments.tf @@ -66,6 +66,87 @@ locals { mtu = try(v.mtu, local.vpcs[v.vpc_key].mtu, local.vpc_defaults.mtu, 1500) }) } + + _attachment_groups_files = try( + merge([ + for vpc_key, vpc in local.vpcs : { + for f in try(fileset( + try( + startswith(vpc.factories_config.attachment_groups, "/") || startswith(vpc.factories_config.attachment_groups, ".") ? vpc.factories_config.attachment_groups : + "${vpc.factory_basepath}/${vpc.factories_config.attachment_groups}", + "${vpc.factory_basepath}/attachment-groups" + ), + "**/*.yaml" + ), []) : + "${vpc_key}-${replace(f, ".yaml", "")}" => { + vpc_key = vpc_key + filename = f + path = try( + startswith(vpc.factories_config.attachment_groups, "/") || startswith(vpc.factories_config.attachment_groups, ".") + ? "${vpc.factories_config.attachment_groups}/${f}" + : "${vpc.factory_basepath}/${vpc.factories_config.attachment_groups}/${f}", + "${vpc.factory_basepath}/attachment-groups/${f}" + ) + } + } + ]...), + {} + ) + _attachment_groups_preprocess = { + for k, v in local._attachment_groups_files : k => merge( + { + project_id = local.vpcs[v.vpc_key].project_id + }, + try(yamldecode(file(v.path)), {}), + { + key = k + vpc_key = v.vpc_key + } + ) + } + attachment_groups = { + for k, v in local._attachment_groups_preprocess : k => merge(v, { + name = try(v.name, k) + intent = try(v.intent, { availability_sla = "NO_SLA" }) + }) + } + + ctx_attachment_groups = { + for k, v in local.attachment_groups : "${v.vpc_key}/${v.name}" => k + } + + ctx_vlan_attachments = { + for k, v in local.vlan_attachments : "${v.vpc_key}/${try(v.name, k)}" => k + } + + # Gathers all members for each attachment group. Membership can be defined + # in two ways: + # 1. From the VLAN attachment's config via the `attachment_group` attribute. + # 2. From the attachment group's config via the `attachments` map. + _attachment_groups_attachments = { + for g_key, g_config in local.attachment_groups : g_key => + concat( + [ + for a_key, a_config in local.vlan_attachments : { + name = try(a_config.name, a_config.key) + attachment = module.vlan-attachments[a_key].id + } + if try( + lookup(local.ctx_attachment_groups, replace(a_config.attachment_group, "$attachment_groups:", ""), a_config.attachment_group), + null + ) == g_key || (try(a_config.attachment_group, null) == g_config.name && a_config.vpc_key == g_config.vpc_key) + ], + [ + for a in values(try(g_config.attachments, {})) : { + name = a.name + attachment = try( + module.vlan-attachments[lookup(local.ctx_vlan_attachments, replace(a.attachment, "$vlan_attachments:", ""), a.attachment)].id, + a.attachment + ) + } + ] + ) + } } module "vlan-attachments" { @@ -95,3 +176,28 @@ module "vlan-attachments" { } depends_on = [module.vpc-factory] } + +resource "google_compute_interconnect_attachment_group" "default" { + for_each = local.attachment_groups + project = lookup( + local.ctx_projects.project_ids, + replace(each.value.project_id, "$project_ids:", ""), + each.value.project_id + ) + name = each.value.name + description = try(each.value.description, "Terraform-managed.") + + intent { + availability_sla = try(each.value.intent.availability_sla, "NO_SLA") + } + + dynamic "attachments" { + for_each = local._attachment_groups_attachments[each.key] + content { + name = attachments.value.name + attachment = attachments.value.attachment + } + } + + depends_on = [module.vlan-attachments] +} diff --git a/fast/stages/2-networking/schemas/attachment-groups.schema.json b/fast/stages/2-networking/schemas/attachment-groups.schema.json new file mode 100644 index 000000000..f6ede53e1 --- /dev/null +++ b/fast/stages/2-networking/schemas/attachment-groups.schema.json @@ -0,0 +1,56 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/blob/master/fast/stages/2-networking/schemas/attachment-groups.schema.json", + "title": "Attachment Groups schema", + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "project_id": { + "type": "string" + }, + "description": { + "type": "string" + }, + "intent": { + "type": "object", + "additionalProperties": false, + "properties": { + "availability_sla": { + "type": "string", + "enum": [ + "NO_SLA", + "PRODUCTION_NON_CRITICAL", + "PRODUCTION_CRITICAL", + "AVAILABILITY_SLA_UNSPECIFIED" + ], + "default": "NO_SLA" + } + } + }, + "attachments": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^[a-zA-Z0-9-_]+$": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "attachment" + ], + "properties": { + "name": { + "type": "string" + }, + "attachment": { + "type": "string" + } + } + } + } + } + } +} diff --git a/fast/stages/2-networking/schemas/attachment-groups.schema.md b/fast/stages/2-networking/schemas/attachment-groups.schema.md new file mode 100644 index 000000000..504e47118 --- /dev/null +++ b/fast/stages/2-networking/schemas/attachment-groups.schema.md @@ -0,0 +1,23 @@ +# Attachment Groups schema + + + +## Properties + +*additional properties: false* + +- **name**: *string* +- **project_id**: *string* +- **description**: *string* +- **intent**: *object* +
*additional properties: false* + - **availability_sla**: *string* +
*default: NO_SLA*, *enum: ['NO_SLA', 'PRODUCTION_NON_CRITICAL', 'PRODUCTION_CRITICAL', 'AVAILABILITY_SLA_UNSPECIFIED']* +- **attachments**: *object* +
*additional properties: false* + - **`^[a-zA-Z0-9-_]+$`**: *object* +
*additional properties: false* + - ⁺**name**: *string* + - ⁺**attachment**: *string* + +## Definitions diff --git a/fast/stages/2-networking/schemas/vlan-attachments.schema.json b/fast/stages/2-networking/schemas/vlan-attachments.schema.json index 2b4f74e59..8cf51587d 100644 --- a/fast/stages/2-networking/schemas/vlan-attachments.schema.json +++ b/fast/stages/2-networking/schemas/vlan-attachments.schema.json @@ -13,6 +13,9 @@ "type": "boolean", "default": true }, + "attachment_group": { + "type": "string" + }, "dedicated_interconnect_config": { "type": "object", "additionalProperties": false, diff --git a/fast/stages/2-networking/schemas/vlan-attachments.schema.md b/fast/stages/2-networking/schemas/vlan-attachments.schema.md index a39117016..63bb710e8 100644 --- a/fast/stages/2-networking/schemas/vlan-attachments.schema.md +++ b/fast/stages/2-networking/schemas/vlan-attachments.schema.md @@ -7,6 +7,7 @@ *additional properties: false* - **admin_enabled**: *boolean* +- **attachment_group**: *string* - **dedicated_interconnect_config**: *object*
*additional properties: false* - **bandwidth**: *string* diff --git a/fast/stages/2-networking/schemas/vpc.schema.json b/fast/stages/2-networking/schemas/vpc.schema.json index 311ededb9..59289339a 100644 --- a/fast/stages/2-networking/schemas/vpc.schema.json +++ b/fast/stages/2-networking/schemas/vpc.schema.json @@ -25,6 +25,9 @@ "firewall_rules": { "type": "string" }, + "attachment_groups": { + "type": "string" + }, "subnets": { "type": "string" }, diff --git a/fast/stages/2-networking/schemas/vpc.schema.md b/fast/stages/2-networking/schemas/vpc.schema.md index d35f0eeef..990e7fa8f 100644 --- a/fast/stages/2-networking/schemas/vpc.schema.md +++ b/fast/stages/2-networking/schemas/vpc.schema.md @@ -12,6 +12,7 @@ - **factories_config**: *object*
*additional properties: false* - **firewall_rules**: *string* + - **attachment_groups**: *string* - **subnets**: *string* - **vlan_attachments**: *string* - **vpns**: *string* diff --git a/tests/fast/stages/s2_networking/data-testvlan/vpcs/hub/attachment-groups/test-group.yaml b/tests/fast/stages/s2_networking/data-testvlan/vpcs/hub/attachment-groups/test-group.yaml new file mode 100644 index 000000000..ff6cedf82 --- /dev/null +++ b/tests/fast/stages/s2_networking/data-testvlan/vpcs/hub/attachment-groups/test-group.yaml @@ -0,0 +1,19 @@ +# Copyright 2026 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=../../../../../schemas/attachment-groups.schema.json + +name: test-group +intent: + availability_sla: PRODUCTION_NON_CRITICAL diff --git a/tests/fast/stages/s2_networking/data-testvlan/vpcs/hub/vlan-attachments/onprem-0.yaml b/tests/fast/stages/s2_networking/data-testvlan/vpcs/hub/vlan-attachments/onprem-0.yaml index 77eed92e3..5ce067651 100644 --- a/tests/fast/stages/s2_networking/data-testvlan/vpcs/hub/vlan-attachments/onprem-0.yaml +++ b/tests/fast/stages/s2_networking/data-testvlan/vpcs/hub/vlan-attachments/onprem-0.yaml @@ -15,6 +15,7 @@ # yaml-language-server: $schema=../../../../../schemas/vlan-attachments.schema.json name: to-onprem-vlan-0 +attachment_group: $attachment_groups:hub/test-group region: $locations:primary router_config: create: false diff --git a/tests/fast/stages/s2_networking/data-testvlan/vpcs/hub/vlan-attachments/onprem-1.yaml b/tests/fast/stages/s2_networking/data-testvlan/vpcs/hub/vlan-attachments/onprem-1.yaml index 78d6ba43b..e6d1d7b7e 100644 --- a/tests/fast/stages/s2_networking/data-testvlan/vpcs/hub/vlan-attachments/onprem-1.yaml +++ b/tests/fast/stages/s2_networking/data-testvlan/vpcs/hub/vlan-attachments/onprem-1.yaml @@ -15,6 +15,7 @@ # yaml-language-server: $schema=../../../../../schemas/vlan-attachments.schema.json name: to-onprem-vlan-1 +attachment_group: $attachment_groups:hub/test-group region: $locations:primary router_config: create: false diff --git a/tests/fast/stages/s2_networking/vlan_attachments.yaml b/tests/fast/stages/s2_networking/vlan_attachments.yaml index c4da065e3..7b17afeab 100644 --- a/tests/fast/stages/s2_networking/vlan_attachments.yaml +++ b/tests/fast/stages/s2_networking/vlan_attachments.yaml @@ -21,12 +21,24 @@ values: labels: null name: hub-to-onprem network: hub-0 + params: [] project: fast-prod-net-core-0 region: europe-west1 stack_type: IPV4_ONLY terraform_labels: goog-terraform-provisioned: 'true' timeouts: null + google_compute_interconnect_attachment_group.default["hub-test-group"]: + attachments: + - name: to-onprem-vlan-0 + - name: to-onprem-vlan-1 + description: Terraform-managed. + intent: + - availability_sla: PRODUCTION_NON_CRITICAL + interconnect_group: null + name: test-group + project: fast-prod-net-core-0 + timeouts: null google_compute_router.default["hub/hybrid-connectivity-router"]: bgp: - advertise_mode: DEFAULT @@ -38,6 +50,7 @@ values: encrypted_interconnect_router: null md5_authentication_keys: [] name: hub-hybrid-connectivity-router + params: [] project: fast-prod-net-core-0 region: europe-west1 timeouts: null @@ -78,7 +91,10 @@ values: linked_router_appliance_instances: [] linked_vpc_network: [] linked_vpn_tunnels: - - include_import_ranges: + - exclude_export_ranges: null + exclude_import_ranges: null + include_export_ranges: null + include_import_ranges: - ALL_IPV4_RANGES site_to_site_data_transfer: true location: europe-west1 @@ -93,7 +109,10 @@ values: goog-terraform-provisioned: 'true' labels: null linked_interconnect_attachments: - - include_import_ranges: + - exclude_export_ranges: null + exclude_import_ranges: null + include_export_ranges: null + include_import_ranges: - ALL_IPV4_RANGES site_to_site_data_transfer: true linked_producer_vpc_network: [] @@ -112,7 +131,10 @@ values: goog-terraform-provisioned: 'true' labels: null linked_interconnect_attachments: - - include_import_ranges: + - exclude_export_ranges: null + exclude_import_ranges: null + include_export_ranges: null + include_import_ranges: - ALL_IPV4_RANGES site_to_site_data_transfer: true linked_producer_vpc_network: [] @@ -143,27 +165,11 @@ values: source: null temporary_hold: null timeouts: null - google_storage_bucket_object.version[0]: - bucket: test - cache_control: null - content_disposition: null - content_encoding: null - content_language: null - contexts: [] - customer_encryption: [] - deletion_policy: null - detect_md5hash: null - event_based_hold: null - force_empty_content_type: null - metadata: null - name: versions/2-networking-version.txt - retention: [] - source: fast_version.txt - temporary_hold: null - timeouts: null module.projects.module.projects-iam["net-core-0"].google_compute_shared_vpc_host_project.shared_vpc_host[0]: project: fast-prod-net-core-0 timeouts: null + module.projects.module.projects["net-core-0"].data.google_logging_project_settings.logging_sa[0]: + project: fast-prod-net-core-0 module.projects.module.projects["net-core-0"].google_project.project[0]: auto_create_network: false billing_account: 000000-111111-222222 @@ -195,6 +201,10 @@ values: condition: [] project: fast-prod-net-core-0 role: roles/container.defaultNodeServiceAgent + module.projects.module.projects["net-core-0"].google_project_iam_member.service_agents["monitoring-notification"]: + condition: [] + project: fast-prod-net-core-0 + role: roles/monitoring.notificationServiceAgent module.projects.module.projects["net-core-0"].google_project_iam_member.service_agents["networkmanagement"]: condition: [] project: fast-prod-net-core-0 @@ -231,6 +241,18 @@ values: project: fast-prod-net-core-0 service: iap.googleapis.com timeouts: null + module.projects.module.projects["net-core-0"].google_project_service.project_services["logging.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-net-core-0 + service: logging.googleapis.com + timeouts: null + module.projects.module.projects["net-core-0"].google_project_service.project_services["monitoring.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-net-core-0 + service: monitoring.googleapis.com + timeouts: null module.projects.module.projects["net-core-0"].google_project_service.project_services["networkmanagement.googleapis.com"]: disable_dependent_services: false disable_on_destroy: false @@ -249,18 +271,6 @@ values: project: fast-prod-net-core-0 service: servicenetworking.googleapis.com timeouts: null - module.projects.module.projects["net-core-0"].google_project_service.project_services["logging.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-net-core-0 - service: logging.googleapis.com - timeouts: null - module.projects.module.projects["net-core-0"].google_project_service.project_services["monitoring.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-net-core-0 - service: monitoring.googleapis.com - timeouts: null module.projects.module.projects["net-core-0"].google_project_service.project_services["vpcaccess.googleapis.com"]: disable_dependent_services: false disable_on_destroy: false @@ -279,6 +289,10 @@ values: project: fast-prod-net-core-0 service: iap.googleapis.com timeouts: null + module.projects.module.projects["net-core-0"].google_project_service_identity.default["monitoring.googleapis.com"]: + project: fast-prod-net-core-0 + service: monitoring.googleapis.com + timeouts: null module.projects.module.projects["net-core-0"].google_project_service_identity.default["networkmanagement.googleapis.com"]: project: fast-prod-net-core-0 service: networkmanagement.googleapis.com @@ -322,6 +336,7 @@ values: labels: null mtu: '1500' name: to-onprem-vlan-0 + params: [] project: fast-prod-net-core-0 region: europe-west1 router: hub-hybrid-connectivity-router @@ -387,6 +402,7 @@ values: labels: null mtu: '1500' name: to-onprem-vlan-1 + params: [] project: fast-prod-net-core-0 region: europe-west1 router: hub-hybrid-connectivity-router @@ -444,6 +460,7 @@ values: name: hub-0 network_firewall_policy_enforcement_order: AFTER_CLASSIC_FIREWALL network_profile: null + params: [] project: fast-prod-net-core-0 routing_mode: GLOBAL timeouts: null @@ -456,6 +473,7 @@ values: next_hop_ilb: null next_hop_instance: null next_hop_vpn_tunnel: null + params: [] priority: 1000 project: fast-prod-net-core-0 tags: null @@ -469,6 +487,7 @@ values: next_hop_ilb: null next_hop_instance: null next_hop_vpn_tunnel: null + params: [] priority: 1000 project: fast-prod-net-core-0 tags: null @@ -482,6 +501,7 @@ values: next_hop_ilb: null next_hop_instance: null next_hop_vpn_tunnel: null + params: [] priority: 1000 project: fast-prod-net-core-0 tags: null @@ -494,6 +514,7 @@ values: log_config: [] name: hub-default network: hub-0 + params: [] private_ip_google_access: true project: fast-prod-net-core-0 region: europe-west1 @@ -511,6 +532,7 @@ values: next_hop_ilb: null next_hop_instance: null next_hop_vpn_tunnel: null + params: [] priority: 1000 project: fast-prod-net-core-0 tags: null @@ -525,6 +547,7 @@ values: ipv6_address: null labels: null name: hub-to-onprem-default + params: [] project: fast-prod-net-core-0 redundancy_type: SINGLE_IP_INTERNALLY_REDUNDANT terraform_labels: @@ -606,6 +629,7 @@ values: ike_version: 2 labels: null name: hub-to-onprem-remote-0 + params: [] peer_external_gateway_interface: 0 peer_gcp_gateway: null project: fast-prod-net-core-0 @@ -627,6 +651,7 @@ values: ike_version: 2 labels: null name: hub-to-onprem-remote-1 + params: [] peer_external_gateway_interface: 0 peer_gcp_gateway: null project: fast-prod-net-core-0 @@ -657,6 +682,7 @@ counts: google_compute_external_vpn_gateway: 1 google_compute_ha_vpn_gateway: 1 google_compute_interconnect_attachment: 2 + google_compute_interconnect_attachment_group: 1 google_compute_network: 1 google_compute_route: 4 google_compute_router: 1 @@ -665,6 +691,7 @@ counts: google_compute_shared_vpc_host_project: 1 google_compute_subnetwork: 1 google_compute_vpn_tunnel: 2 + google_logging_project_settings: 1 google_network_connectivity_group: 1 google_network_connectivity_hub: 1 google_network_connectivity_spoke: 3 @@ -675,7 +702,7 @@ counts: google_storage_bucket_object: 2 modules: 9 random_id: 5 - resources: 64 + resources: 65 terraform_data: 2 outputs: @@ -691,3 +718,4 @@ outputs: hub: {} subnet_self_links: __missing__ vpc_self_links: __missing__ +