Add attachment groups factory to 2-networking (#3871)

* feat(fast): add attachment groups factory to 2-networking

Adds support for `google_compute_interconnect_attachment_group` in the `2-networking` stage.

By implementing this at the factory level alongside `vlan-attachments`, users can now declaratively aggregate VLAN attachments across multiple VPCs and natively reference them using the `$attachment_groups:<key>` context identifier in their configuration YAMLs.

Includes:
- Factory implementation in `factory-vlan-attachments.tf`.
- New JSON schemas for `attachment-groups` and updates to `vlan-attachments` to support context linkage.
- Test coverage with new inventory generations.

Fixes #3791
This commit is contained in:
Simone Ruffilli
2026-04-22 12:22:50 +02:00
committed by GitHub
parent ec39405352
commit 9eb69ffaa3
12 changed files with 277 additions and 35 deletions

View File

@@ -367,7 +367,7 @@ Internally created resources are mapped to context namespaces, and use specific
| [factory-peering.tf](./factory-peering.tf) | VPC Peering factory. | | <code>google_compute_network_peering</code> |
| [factory-projects.tf](./factory-projects.tf) | Projects factory. | <code>project-factory</code> | |
| [factory-routers.tf](./factory-routers.tf) | Routers factory. | | <code>google_compute_router</code> |
| [factory-vlan-attachments.tf](./factory-vlan-attachments.tf) | VLAN attachments factory. | <code>net-vlan-attachment</code> | |
| [factory-vlan-attachments.tf](./factory-vlan-attachments.tf) | VLAN attachments factory. | <code>net-vlan-attachment</code> | <code>google_compute_interconnect_attachment_group</code> |
| [factory-vpcs.tf](./factory-vpcs.tf) | VPC and firewall rules factory. | <code>net-vpc</code> · <code>net-vpc-factory</code> | |
| [factory-vpns.tf](./factory-vpns.tf) | VPNs factory. | <code>net-vpn-ha</code> | <code>google_compute_ha_vpn_gateway</code> |
| [main.tf](./main.tf) | Module-level locals and resources. | | |

View File

@@ -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]
}

View File

@@ -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"
}
}
}
}
}
}
}

View File

@@ -0,0 +1,23 @@
# Attachment Groups schema
<!-- markdownlint-disable MD036 -->
## Properties
*additional properties: false*
- **name**: *string*
- **project_id**: *string*
- **description**: *string*
- **intent**: *object*
<br>*additional properties: false*
- **availability_sla**: *string*
<br>*default: NO_SLA*, *enum: ['NO_SLA', 'PRODUCTION_NON_CRITICAL', 'PRODUCTION_CRITICAL', 'AVAILABILITY_SLA_UNSPECIFIED']*
- **attachments**: *object*
<br>*additional properties: false*
- **`^[a-zA-Z0-9-_]+$`**: *object*
<br>*additional properties: false*
- ⁺**name**: *string*
- ⁺**attachment**: *string*
## Definitions

View File

@@ -13,6 +13,9 @@
"type": "boolean",
"default": true
},
"attachment_group": {
"type": "string"
},
"dedicated_interconnect_config": {
"type": "object",
"additionalProperties": false,

View File

@@ -7,6 +7,7 @@
*additional properties: false*
- **admin_enabled**: *boolean*
- **attachment_group**: *string*
- **dedicated_interconnect_config**: *object*
<br>*additional properties: false*
- **bandwidth**: *string*

View File

@@ -25,6 +25,9 @@
"firewall_rules": {
"type": "string"
},
"attachment_groups": {
"type": "string"
},
"subnets": {
"type": "string"
},

View File

@@ -12,6 +12,7 @@
- **factories_config**: *object*
<br>*additional properties: false*
- **firewall_rules**: *string*
- **attachment_groups**: *string*
- **subnets**: *string*
- **vlan_attachments**: *string*
- **vpns**: *string*

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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__