From fa8ef5e34d3408f82c012ea6fcf4bfa357ae2a46 Mon Sep 17 00:00:00 2001 From: Camillo Rossi Date: Thu, 14 Aug 2025 16:03:15 +1000 Subject: [PATCH 1/3] Add ESG external subnet selectors support - Add external_subnet_selectors to endpoint security groups - Support for external subnet selection in ESG configuration - Update documentation and examples for external subnet selectors - Include validation and proper variable definitions --- aci_tenants.tf | 40 +++++++++++-------- .../README.md | 16 ++++++++ .../examples/complete/README.md | 16 ++++++++ .../examples/complete/main.tf | 16 ++++++++ .../main.tf | 14 +++++++ .../variables.tf | 24 +++++++++++ 6 files changed, 109 insertions(+), 17 deletions(-) diff --git a/aci_tenants.tf b/aci_tenants.tf index 05059404..9a62ffc9 100644 --- a/aci_tenants.tf +++ b/aci_tenants.tf @@ -694,6 +694,11 @@ locals { value = sel.value description = try(sel.description, "") }] + ip_external_subnet_selectors = [for sel in try(esg.ip_external_subnet_selectors, []) : { + value = sel.value + description = try(sel.description, "") + shared = try(sel.shared, "false") + }] } ] ] @@ -703,23 +708,24 @@ locals { module "aci_endpoint_security_group" { source = "./modules/terraform-aci-endpoint-security-group" - for_each = { for esg in local.endpoint_security_groups : esg.key => esg if local.modules.aci_endpoint_security_group && var.manage_tenants } - tenant = each.value.tenant - application_profile = each.value.application_profile - name = each.value.name - description = each.value.description - vrf = each.value.vrf - shutdown = each.value.shutdown - intra_esg_isolation = each.value.intra_esg_isolation - preferred_group = each.value.preferred_group - contract_consumers = each.value.contract_consumers - contract_providers = each.value.contract_providers - contract_imported_consumers = each.value.contract_imported_consumers - contract_intra_esgs = each.value.contract_intra_esgs - esg_contract_masters = each.value.esg_contract_masters - tag_selectors = each.value.tag_selectors - epg_selectors = each.value.epg_selectors - ip_subnet_selectors = each.value.ip_subnet_selectors + for_each = { for esg in local.endpoint_security_groups : esg.key => esg if local.modules.aci_endpoint_security_group && var.manage_tenants } + tenant = each.value.tenant + application_profile = each.value.application_profile + name = each.value.name + description = each.value.description + vrf = each.value.vrf + shutdown = each.value.shutdown + intra_esg_isolation = each.value.intra_esg_isolation + preferred_group = each.value.preferred_group + contract_consumers = each.value.contract_consumers + contract_providers = each.value.contract_providers + contract_imported_consumers = each.value.contract_imported_consumers + contract_intra_esgs = each.value.contract_intra_esgs + esg_contract_masters = each.value.esg_contract_masters + tag_selectors = each.value.tag_selectors + epg_selectors = each.value.epg_selectors + ip_subnet_selectors = each.value.ip_subnet_selectors + ip_external_subnet_selectors = each.value.ip_external_subnet_selectors depends_on = [ module.aci_tenant, diff --git a/modules/terraform-aci-endpoint-security-group/README.md b/modules/terraform-aci-endpoint-security-group/README.md index 8a03e4e8..f66f8fde 100644 --- a/modules/terraform-aci-endpoint-security-group/README.md +++ b/modules/terraform-aci-endpoint-security-group/README.md @@ -75,6 +75,22 @@ module "aci_endpoint_security_group" { description = "foo" } ] + ip_external_subnet_selectors = [ + { + value = "1.1.5.0/24" + }, + { + value = "1.1.6.0/24" + }, + { + value = "1.1.7.0/24" + }, + { + value = "1.1.8.0/24" + description = "foo" + shared = true + } + ] } ``` diff --git a/modules/terraform-aci-endpoint-security-group/examples/complete/README.md b/modules/terraform-aci-endpoint-security-group/examples/complete/README.md index 314a8239..099b3063 100644 --- a/modules/terraform-aci-endpoint-security-group/examples/complete/README.md +++ b/modules/terraform-aci-endpoint-security-group/examples/complete/README.md @@ -78,6 +78,22 @@ module "aci_endpoint_security_group" { description = "foo" } ] +ip_external_subnet_selectors = [ + { + value = "1.1.5.0/24" + }, + { + value = "1.1.6.0/24" + }, + { + value = "1.1.7.0/24" + }, + { + value = "1.1.8.0/24" + description = "foo" + shared = true + } + ] } ``` \ No newline at end of file diff --git a/modules/terraform-aci-endpoint-security-group/examples/complete/main.tf b/modules/terraform-aci-endpoint-security-group/examples/complete/main.tf index d2b2eb14..7a237788 100644 --- a/modules/terraform-aci-endpoint-security-group/examples/complete/main.tf +++ b/modules/terraform-aci-endpoint-security-group/examples/complete/main.tf @@ -64,4 +64,20 @@ module "aci_endpoint_security_group" { description = "foo" } ] + ip_external_subnet_selectors = [ + { + value = "1.1.1.0/24" + }, + { + value = "1.1.2.0/24" + }, + { + value = "1.1.3.0/24" + }, + { + value = "1.1.4.0/24" + description = "foo" + shared = true + } + ] } diff --git a/modules/terraform-aci-endpoint-security-group/main.tf b/modules/terraform-aci-endpoint-security-group/main.tf index 532008c8..1d53750e 100644 --- a/modules/terraform-aci-endpoint-security-group/main.tf +++ b/modules/terraform-aci-endpoint-security-group/main.tf @@ -127,3 +127,17 @@ resource "aci_rest_managed" "fvEPSelector" { aci_rest_managed.fvRsScope, ] } + +resource "aci_rest_managed" "fvExternalSubnetSelector" { + for_each = { for ess in var.ip_external_subnet_selectors : "${ess.value}" => ess } + dn = "${aci_rest_managed.fvESg.dn}/extsubselector-[${each.key}]" + class_name = "fvExternalSubnetSelector" + content = { + descr = each.value.description + shared = each.value.shared + } + + depends_on = [ + aci_rest_managed.fvRsScope, + ] +} \ No newline at end of file diff --git a/modules/terraform-aci-endpoint-security-group/variables.tf b/modules/terraform-aci-endpoint-security-group/variables.tf index b82926fe..4e981641 100644 --- a/modules/terraform-aci-endpoint-security-group/variables.tf +++ b/modules/terraform-aci-endpoint-security-group/variables.tf @@ -250,3 +250,27 @@ variable "ip_subnet_selectors" { error_message = "`description`: Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `\\`, `!`, `#`, `$`, `%`, `(`, `)`, `*`, `,`, `-`, `.`, `/`, `:`, `;`, `@`, ` `, `_`, `{`, `|`, }`, `~`, `?`, `&`, `+`. Maximum characters: 128." } } + +variable "ip_external_subnet_selectors" { + description = "List of IP subnet selectors." + type = list(object({ + value = string + description = optional(string, "") + shared = optional(bool, false) + })) + default = [] + + validation { + condition = alltrue([ + for ess in var.ip_external_subnet_selectors : can(regex("^([0-9]{1,3}\\.){3}[0-9]{1,3}\\/([0-9]){1,2}$", ess.value)) + ]) + error_message = "`value`: Valid ip format example: 192.168.1.0/24." + } + + validation { + condition = alltrue([ + for ess in var.ip_external_subnet_selectors : ess.description == null || can(regex("^[a-zA-Z0-9\\\\!#$%()*,-./:;@ _{|}~?&+]{0,128}$", ess.description)) + ]) + error_message = "`description`: Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `\\`, `!`, `#`, `$`, `%`, `(`, `)`, `*`, `,`, `-`, `.`, `/`, `:`, `;`, `@`, ` `, `_`, `{`, `|`, }`, `~`, `?`, `&`, `+`. Maximum characters: 128." + } +} \ No newline at end of file From 7ce024e3b4a0ee8f67b048756d1f16dc375f804c Mon Sep 17 00:00:00 2001 From: Camillo Rossi Date: Tue, 19 Aug 2025 14:08:56 +1000 Subject: [PATCH 2/3] fix shared idempotency and renamed value to ip --- aci_tenants.tf | 8 +++++++- modules/terraform-aci-endpoint-security-group/README.md | 8 ++++---- .../examples/complete/main.tf | 8 ++++---- modules/terraform-aci-endpoint-security-group/main.tf | 4 ++-- .../terraform-aci-endpoint-security-group/variables.tf | 6 +++--- 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/aci_tenants.tf b/aci_tenants.tf index 9a62ffc9..19f97cde 100644 --- a/aci_tenants.tf +++ b/aci_tenants.tf @@ -695,7 +695,7 @@ locals { description = try(sel.description, "") }] ip_external_subnet_selectors = [for sel in try(esg.ip_external_subnet_selectors, []) : { - value = sel.value + ip = sel.ip description = try(sel.description, "") shared = try(sel.shared, "false") }] @@ -2516,6 +2516,9 @@ locals { external_endpoint_group = try(policy.external_endpoint_group.name, null) != null ? "${policy.external_endpoint_group.name}${local.defaults.apic.tenants.l3outs.external_endpoint_groups.name_suffix}" : "" external_endpoint_group_l3out = try(policy.external_endpoint_group.l3out, null) != null ? "${policy.external_endpoint_group.l3out}${local.defaults.apic.tenants.l3outs.name_suffix}" : "" external_endpoint_group_tenant = try(policy.external_endpoint_group.tenant, tenant.name) + endpoint_security_group = try(policy.endpoint_security_group.name, null) != null ? "${policy.endpoint_security_group.name}${local.defaults.apic.tenants.application_profiles.name_suffix}" : "" + endpoint_security_group_app = try(policy.endpoint_security_group.app, null) != null ? "${policy.endpoint_security_group.app}${local.defaults.apic.tenants.application_profiles.endpoint_security_groups.name_suffix}" : "" + endpoint_security_group_tenant = try(policy.endpoint_security_group.tenant, tenant.name) } ] ]) @@ -2548,6 +2551,9 @@ module "aci_set_rule" { external_endpoint_group = each.value.external_endpoint_group external_endpoint_group_l3out = each.value.external_endpoint_group_l3out external_endpoint_group_tenant = each.value.external_endpoint_group_tenant + endpoint_security_group = each.value.endpoint_security_group + endpoint_security_group_app = each.value.endpoint_security_group_app + endpoint_security_group_tenant = each.value.endpoint_security_group_tenant depends_on = [ module.aci_tenant, diff --git a/modules/terraform-aci-endpoint-security-group/README.md b/modules/terraform-aci-endpoint-security-group/README.md index f66f8fde..aa4b889f 100644 --- a/modules/terraform-aci-endpoint-security-group/README.md +++ b/modules/terraform-aci-endpoint-security-group/README.md @@ -77,16 +77,16 @@ module "aci_endpoint_security_group" { ] ip_external_subnet_selectors = [ { - value = "1.1.5.0/24" + ip = "1.1.5.0/24" }, { - value = "1.1.6.0/24" + ip = "1.1.6.0/24" }, { - value = "1.1.7.0/24" + ip = "1.1.7.0/24" }, { - value = "1.1.8.0/24" + ip = "1.1.8.0/24" description = "foo" shared = true } diff --git a/modules/terraform-aci-endpoint-security-group/examples/complete/main.tf b/modules/terraform-aci-endpoint-security-group/examples/complete/main.tf index 7a237788..975da419 100644 --- a/modules/terraform-aci-endpoint-security-group/examples/complete/main.tf +++ b/modules/terraform-aci-endpoint-security-group/examples/complete/main.tf @@ -66,16 +66,16 @@ module "aci_endpoint_security_group" { ] ip_external_subnet_selectors = [ { - value = "1.1.1.0/24" + ip = "1.1.1.0/24" }, { - value = "1.1.2.0/24" + ip = "1.1.2.0/24" }, { - value = "1.1.3.0/24" + ip = "1.1.3.0/24" }, { - value = "1.1.4.0/24" + ip = "1.1.4.0/24" description = "foo" shared = true } diff --git a/modules/terraform-aci-endpoint-security-group/main.tf b/modules/terraform-aci-endpoint-security-group/main.tf index 1d53750e..837cb68a 100644 --- a/modules/terraform-aci-endpoint-security-group/main.tf +++ b/modules/terraform-aci-endpoint-security-group/main.tf @@ -129,12 +129,12 @@ resource "aci_rest_managed" "fvEPSelector" { } resource "aci_rest_managed" "fvExternalSubnetSelector" { - for_each = { for ess in var.ip_external_subnet_selectors : "${ess.value}" => ess } + for_each = { for ess in var.ip_external_subnet_selectors : "${ess.ip}" => ess } dn = "${aci_rest_managed.fvESg.dn}/extsubselector-[${each.key}]" class_name = "fvExternalSubnetSelector" content = { descr = each.value.description - shared = each.value.shared + shared = each.value.shared == true ? "yes" : "no" } depends_on = [ diff --git a/modules/terraform-aci-endpoint-security-group/variables.tf b/modules/terraform-aci-endpoint-security-group/variables.tf index 4e981641..5ec25ebf 100644 --- a/modules/terraform-aci-endpoint-security-group/variables.tf +++ b/modules/terraform-aci-endpoint-security-group/variables.tf @@ -254,7 +254,7 @@ variable "ip_subnet_selectors" { variable "ip_external_subnet_selectors" { description = "List of IP subnet selectors." type = list(object({ - value = string + ip = string description = optional(string, "") shared = optional(bool, false) })) @@ -262,9 +262,9 @@ variable "ip_external_subnet_selectors" { validation { condition = alltrue([ - for ess in var.ip_external_subnet_selectors : can(regex("^([0-9]{1,3}\\.){3}[0-9]{1,3}\\/([0-9]){1,2}$", ess.value)) + for ess in var.ip_external_subnet_selectors : can(regex("^([0-9]{1,3}\\.){3}[0-9]{1,3}\\/([0-9]){1,2}$", ess.ip)) ]) - error_message = "`value`: Valid ip format example: 192.168.1.0/24." + error_message = "`ip`: Valid ip format example: 192.168.1.0/24." } validation { From f336d95bef0d2b2955e932d5c6fff99283c0a3e2 Mon Sep 17 00:00:00 2001 From: Camillo Rossi Date: Tue, 19 Aug 2025 22:50:48 +1000 Subject: [PATCH 3/3] remove aci_set_rule changes --- aci_tenants.tf | 3 --- 1 file changed, 3 deletions(-) diff --git a/aci_tenants.tf b/aci_tenants.tf index 19f97cde..9fa1a4f8 100644 --- a/aci_tenants.tf +++ b/aci_tenants.tf @@ -2551,9 +2551,6 @@ module "aci_set_rule" { external_endpoint_group = each.value.external_endpoint_group external_endpoint_group_l3out = each.value.external_endpoint_group_l3out external_endpoint_group_tenant = each.value.external_endpoint_group_tenant - endpoint_security_group = each.value.endpoint_security_group - endpoint_security_group_app = each.value.endpoint_security_group_app - endpoint_security_group_tenant = each.value.endpoint_security_group_tenant depends_on = [ module.aci_tenant,