From 4060e4474fefefe75fb4f3e9926e1f37e371a3b6 Mon Sep 17 00:00:00 2001 From: Andrew Byrne Date: Thu, 23 May 2024 16:20:34 +1000 Subject: [PATCH 1/2] Add support for uSeg EPGs Co-authored-by: guyan --- .pre-commit-config.yaml | 4 + README.md | 1 + aci_tenants.tf | 131 ++++++ defaults/defaults.yaml | 29 ++ defaults/modules.yaml | 1 + .../.terraform-docs.yml | 34 ++ .../README.md | 167 ++++++++ .../examples/complete/.terraform-docs.yml | 24 ++ .../examples/complete/README.md | 86 ++++ .../examples/complete/main.tf | 70 +++ .../examples/complete/versions.tf | 11 + .../terraform-aci-useg-endpoint-group/main.tf | 300 +++++++++++++ .../outputs.tf | 9 + .../variables.tf | 397 ++++++++++++++++++ .../versions.tf | 11 + 15 files changed, 1275 insertions(+) create mode 100644 modules/terraform-aci-useg-endpoint-group/.terraform-docs.yml create mode 100644 modules/terraform-aci-useg-endpoint-group/README.md create mode 100644 modules/terraform-aci-useg-endpoint-group/examples/complete/.terraform-docs.yml create mode 100644 modules/terraform-aci-useg-endpoint-group/examples/complete/README.md create mode 100644 modules/terraform-aci-useg-endpoint-group/examples/complete/main.tf create mode 100644 modules/terraform-aci-useg-endpoint-group/examples/complete/versions.tf create mode 100644 modules/terraform-aci-useg-endpoint-group/main.tf create mode 100644 modules/terraform-aci-useg-endpoint-group/outputs.tf create mode 100644 modules/terraform-aci-useg-endpoint-group/variables.tf create mode 100644 modules/terraform-aci-useg-endpoint-group/versions.tf diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9e7638e8..9c1a5f92 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -189,6 +189,10 @@ repos: args: ["./modules/terraform-aci-endpoint-group"] - id: terraform-docs-system args: ["./modules/terraform-aci-endpoint-group/examples/complete"] + - id: terraform-docs-system + args: ["./modules/terraform-aci-useg-endpoint-group"] + - id: terraform-docs-system + args: ["./modules/terraform-aci-useg-endpoint-group/examples/complete"] - id: terraform-docs-system args: ["./modules/terraform-aci-endpoint-loop-protection"] - id: terraform-docs-system diff --git a/README.md b/README.md index 39cd5410..653d7fa7 100644 --- a/README.md +++ b/README.md @@ -308,6 +308,7 @@ Additional example repositories: | [aci\_tenant\_span\_destination\_group](#module\_aci\_tenant\_span\_destination\_group) | ./modules/terraform-aci-tenant-span-destination-group | n/a | | [aci\_tenant\_span\_source\_group](#module\_aci\_tenant\_span\_source\_group) | ./modules/terraform-aci-tenant-span-source-group | n/a | | [aci\_trust\_control\_policy](#module\_aci\_trust\_control\_policy) | ./modules/terraform-aci-trust-control-policy | n/a | +| [aci\_useg\_endpoint\_group](#module\_aci\_useg\_endpoint\_group) | ./modules/terraform-aci-useg-endpoint-group | n/a | | [aci\_user](#module\_aci\_user) | ./modules/terraform-aci-user | n/a | | [aci\_vlan\_pool](#module\_aci\_vlan\_pool) | ./modules/terraform-aci-vlan-pool | n/a | | [aci\_vmware\_vmm\_domain](#module\_aci\_vmware\_vmm\_domain) | ./modules/terraform-aci-vmware-vmm-domain | n/a | diff --git a/aci_tenants.tf b/aci_tenants.tf index 9c28a823..088c96c4 100644 --- a/aci_tenants.tf +++ b/aci_tenants.tf @@ -485,6 +485,137 @@ module "aci_endpoint_group" { ] } +locals { + useg_endpoint_groups = flatten([ + for tenant in local.tenants : [ + for ap in try(tenant.application_profiles, []) : [ + for useg_epg in try(ap.useg_endpoint_groups, []) : { + key = format("%s/%s/%s", tenant.name, ap.name, useg_epg.name) + tenant = tenant.name + application_profile = "${ap.name}${local.defaults.apic.tenants.application_profiles.name_suffix}" + name = "${useg_epg.name}${local.defaults.apic.tenants.application_profiles.useg_endpoint_groups.name_suffix}" + alias = try(useg_epg.alias, "") + description = try(useg_epg.description, "") + flood_in_encap = try(useg_epg.flood_in_encap, local.defaults.apic.tenants.application_profiles.useg_endpoint_groups.flood_in_encap) + intra_epg_isolation = try(useg_epg.intra_epg_isolation, local.defaults.apic.tenants.application_profiles.useg_endpoint_groups.intra_epg_isolation) + preferred_group = try(useg_epg.preferred_group, local.defaults.apic.tenants.application_profiles.useg_endpoint_groups.preferred_group) + qos_class = try(useg_epg.qos_class, local.defaults.apic.tenants.application_profiles.useg_endpoint_groups.qos_class) + custom_qos_policy = try("${useg_epg.custom_qos_policy}${local.defaults.apic.tenants.policies.custom_qos.name_suffix}", "") + bridge_domain = try("${useg_epg.bridge_domain}${local.defaults.apic.tenants.bridge_domains.name_suffix}", "") + tags = try(useg_epg.tags, []) + trust_control_policy = try("${useg_epg.trust_control_policy}${local.defaults.apic.tenants.policies.trust_control_policies.name_suffix}", "") + contract_consumers = try([for contract in useg_epg.contracts.consumers : "${contract}${local.defaults.apic.tenants.contracts.name_suffix}"], []) + contract_providers = try([for contract in useg_epg.contracts.providers : "${contract}${local.defaults.apic.tenants.contracts.name_suffix}"], []) + contract_imported_consumers = try([for contract in useg_epg.contracts.imported_consumers : "${contract}${local.defaults.apic.tenants.imported_contracts.name_suffix}"], []) + contract_intra_epgs = try([for contract in useg_epg.contracts.intra_epgs : "${contract}${local.defaults.apic.tenants.contracts.name_suffix}"], []) + physical_domains = try([for domain in useg_epg.physical_domains : "${domain}${local.defaults.apic.access_policies.physical_domains.name_suffix}"], []) + useg_attributes_match_type = try(useg_epg.useg_attributes.match_type, local.defaults.apic.tenants.application_profiles.useg_endpoint_groups.useg_attributes.match_type) + contract_masters = [for master in try(useg_epg.contracts.masters, []) : { + endpoint_group = master.endpoint_group + application_profile = try(master.application_profile, "${ap.name}${local.defaults.apic.tenants.application_profiles.name_suffix}") + }] + useg_attributes_ip_statements = [for ip_statement in try(useg_epg.useg_attributes.ip_statements, []) : { + name = ip_statement.name + use_epg_subnet = try(ip_statement.use_epg_subnet, local.defaults.apic.tenants.application_profiles.useg_endpoint_groups.useg_attributes.ip_statements.use_epg_subnet) + ip = try(ip_statement.ip, "") + }] + useg_attributes_mac_statements = [for mac_statement in try(useg_epg.useg_attributes.mac_statements, []) : { + name = mac_statement.name + mac = upper(mac_statement.mac) + }] + subnets = [for subnet in try(useg_epg.subnets, []) : { + description = try(subnet.description, "") + ip = subnet.ip + public = try(subnet.public, local.defaults.apic.tenants.application_profiles.useg_endpoint_groups.subnets.public) + shared = try(subnet.shared, local.defaults.apic.tenants.application_profiles.useg_endpoint_groups.subnets.shared) + igmp_querier = try(subnet.igmp_querier, local.defaults.apic.tenants.application_profiles.useg_endpoint_groups.subnets.igmp_querier) + nd_ra_prefix = try(subnet.nd_ra_prefix, local.defaults.apic.tenants.application_profiles.useg_endpoint_groups.subnets.nd_ra_prefix) + no_default_gateway = try(subnet.no_default_gateway, local.defaults.apic.tenants.application_profiles.useg_endpoint_groups.subnets.no_default_gateway) + nd_ra_prefix_policy = try("${subnet.nd_ra_prefix_policy}${local.defaults.apic.tenants.policies.nd_ra_prefix_policies.name_suffix}", "") + next_hop_ip = try(subnet.next_hop_ip, "") + anycast_mac = try(subnet.anycast_mac, "") + nlb_group = try(subnet.nlb_group, "0.0.0.0") + nlb_mac = try(subnet.nlb_mac, "00:00:00:00:00:00") + nlb_mode = try(subnet.nlb_mode, "") + ip_pools = [for pool in try(subnet.ip_pools, []) : { + name = "${pool.name}${local.defaults.apic.tenants.application_profiles.useg_endpoint_groups.subnets.ip_pools.name_suffix}" + start_ip = try(pool.start_ip, "0.0.0.0") + end_ip = try(pool.end_ip, "0.0.0.0") + dns_search_suffix = try(pool.dns_search_suffix, "") + dns_server = try(pool.dns_server, "") + dns_suffix = try(pool.dns_suffix, "") + wins_server = try(pool.wins_server, "") + }] + }] + vmware_vmm_domains = [for vmm in try(useg_epg.vmware_vmm_domains, []) : { + name = "${vmm.name}${local.defaults.apic.fabric_policies.vmware_vmm_domains.name_suffix}" + deployment_immediacy = try(vmm.deployment_immediacy, local.defaults.apic.tenants.application_profiles.useg_endpoint_groups.vmware_vmm_domains.deployment_immediacy) + netflow = try(vmm.netflow, local.defaults.apic.tenants.application_profiles.useg_endpoint_groups.vmware_vmm_domains.netflow) + elag = try(vmm.elag, "") + active_uplinks_order = try(vmm.active_uplinks_order, "") + standby_uplinks = try(vmm.standby_uplinks, "") + }] + static_leafs = [for sl in try(useg_epg.static_leafs, []) : { + pod_id = try(sl.pod_id, null) + node_id = try(sl.node_id, null) + }] + l4l7_address_pools = [for ap in try(useg_epg.l4l7_address_pools, []) : { + name = ap.name + gateway_address = ap.gateway_address + from = try(ap.from, "") + to = try(ap.to, "") + }] + } + ] + ] + ]) +} + +module "aci_useg_endpoint_group" { + source = "./modules/terraform-aci-useg-endpoint-group" + + for_each = { for epg in local.useg_endpoint_groups : epg.key => epg if local.modules.aci_useg_endpoint_group && var.manage_tenants } + tenant = each.value.tenant + application_profile = each.value.application_profile + name = each.value.name + alias = each.value.alias + description = each.value.description + flood_in_encap = each.value.flood_in_encap + intra_epg_isolation = each.value.intra_epg_isolation + preferred_group = each.value.preferred_group + qos_class = each.value.qos_class + custom_qos_policy = each.value.custom_qos_policy + bridge_domain = each.value.bridge_domain + tags = each.value.tags + trust_control_policy = each.value.trust_control_policy + contract_consumers = each.value.contract_consumers + contract_providers = each.value.contract_providers + contract_imported_consumers = each.value.contract_imported_consumers + contract_intra_epgs = each.value.contract_intra_epgs + contract_masters = each.value.contract_masters + physical_domains = each.value.physical_domains + match_type = each.value.useg_attributes_match_type + ip_statements = each.value.useg_attributes_ip_statements + mac_statements = each.value.useg_attributes_mac_statements + subnets = each.value.subnets + vmware_vmm_domains = each.value.vmware_vmm_domains + static_leafs = [for sl in try(each.value.static_leafs, []) : { + pod_id = sl.pod_id == null ? try([for node in try(local.node_policies.nodes, []) : node.pod if node.id == sl.node_id][0], local.defaults.apic.node_policies.nodes.pod) : sl.pod_id + node_id = sl.node_id + }] + l4l7_address_pools = each.value.l4l7_address_pools + + depends_on = [ + module.aci_tenant, + module.aci_application_profile, + module.aci_endpoint_group, + module.aci_bridge_domain, + module.aci_contract, + module.aci_imported_contract, + module.aci_vmware_vmm_domain, + ] +} + locals { endpoint_security_groups = flatten([ for tenant in local.tenants : [ diff --git a/defaults/defaults.yaml b/defaults/defaults.yaml index 94ac1363..f2a0083f 100644 --- a/defaults/defaults.yaml +++ b/defaults/defaults.yaml @@ -997,6 +997,35 @@ defaults: name_suffix: "" start_ip: 0.0.0.0 end_ip: 0.0.0.0 + useg_endpoint_groups: + name_suffix: "" + flood_in_encap: false + intra_epg_isolation: false + preferred_group: false + qos_class: level3 + vmware_vmm_domains: + name_suffix: "" + deployment_immediacy: immediate + netflow: false + useg_attributes: + match_type: any + ip_statements: + use_epg_subnet: true + subnets: + primary_ip: false + public: false + private: true + shared: false + igmp_querier: false + nd_ra_prefix: true + no_default_gateway: false + virtual: false + nlb_group: 0.0.0.0 + nlb_mac: 00:00:00:00:00:00 + ip_pools: + name_suffix: "" + start_ip: 0.0.0.0 + end_ip: 0.0.0.0 endpoint_security_groups: name_suffix: "" shutdown: false diff --git a/defaults/modules.yaml b/defaults/modules.yaml index daecd0ef..18544fae 100644 --- a/defaults/modules.yaml +++ b/defaults/modules.yaml @@ -41,6 +41,7 @@ modules: aci_dhcp_relay_policy: true aci_dns_policy: true aci_endpoint_group: true + aci_useg_endpoint_group: true aci_endpoint_loop_protection: true aci_endpoint_security_group: true aci_eigrp_interface_policy: true diff --git a/modules/terraform-aci-useg-endpoint-group/.terraform-docs.yml b/modules/terraform-aci-useg-endpoint-group/.terraform-docs.yml new file mode 100644 index 00000000..9deec4c0 --- /dev/null +++ b/modules/terraform-aci-useg-endpoint-group/.terraform-docs.yml @@ -0,0 +1,34 @@ +version: '>= 0.14.0' + +formatter: markdown table + +content: |- + # Terraform ACI uSeg Endpoint Group Module + + Manages ACI uSeg Endpoint Group + + Location in GUI: + `Tenants` » `XXX` » `Application Profiles` » `XXX` » `uSeg EPGs` + + ## Examples + + ```hcl + {{ include "./examples/complete/main.tf" }} + ``` + + {{ .Requirements }} + + {{ .Providers }} + + {{ .Inputs }} + + {{ .Outputs }} + + {{ .Resources }} + +output: + file: README.md + mode: replace + +sort: + enabled: false diff --git a/modules/terraform-aci-useg-endpoint-group/README.md b/modules/terraform-aci-useg-endpoint-group/README.md new file mode 100644 index 00000000..98357f05 --- /dev/null +++ b/modules/terraform-aci-useg-endpoint-group/README.md @@ -0,0 +1,167 @@ + +# Terraform ACI uSeg Endpoint Group Module + +Manages ACI uSeg Endpoint Group + +Location in GUI: +`Tenants` » `XXX` » `Application Profiles` » `XXX` » `uSeg EPGs` + +## Examples + +```hcl +module "aci_useg_endpoint_group" { + source = "netascode/nac-aci/aci//modules/terraform-aci-endpoint-group" + version = ">= 0.8.0" + + tenant = "ABC" + application_profile = "AP1" + name = "uSeg_EPG1" + alias = "uSeg-EPG1-ALIAS" + description = "My uSeg EPG Description" + flood_in_encap = false + intra_epg_isolation = true + preferred_group = true + qos_class = "level1" + custom_qos_policy = "CQP1" + bridge_domain = "BD1" + trust_control_policy = "TRUST_POL" + contract_consumers = ["CON1"] + contract_providers = ["CON1"] + contract_imported_consumers = ["I_CON1"] + contract_intra_epgs = ["CON1"] + contract_masters = [{ + endpoint_group = "EPG2" + application_profile = "AP1" + }] + physical_domains = ["PHY1"] + tags = [ + "tag1", + "tag2" + ] + + match_type = "any" + ip_statements = [{ + name = "ip_1" + use_epg_subnet = false + ip = "1.2.2.11" + }, { + name = "ip_2" + use_epg_subnet = true + }] + mac_statements = [{ + name = "mac_1" + mac = "02:aa:68:22:58:d1" + }, { + name = "mac_2" + mac = "02:aa:68:22:58:d2" + }] + subnets = [{ + description = "Subnet Description" + ip = "1.2.2.1/24" + public = true + shared = true + igmp_querier = true + nd_ra_prefix = true + no_default_gateway = false + }] + vmware_vmm_domains = [{ + name = "VMW1" + netflow = false + deployment_immediacy = "immediate" + }] + static_leafs = [{ + node_id = 102 + }] + l4l7_address_pools = [{ + name = "POOL1" + gateway_address = "1.2.2.1/24" + from = "1.2.2.10" + to = "1.2.2.100" + }] +} +``` + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.3.0 | +| [aci](#requirement\_aci) | >= 2.0.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aci](#provider\_aci) | >= 2.0.0 | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [tenant](#input\_tenant) | Tenant name. | `string` | n/a | yes | +| [application\_profile](#input\_application\_profile) | Application profile name. | `string` | n/a | yes | +| [name](#input\_name) | uSeg Endpoint group name. | `string` | n/a | yes | +| [alias](#input\_alias) | Alias. | `string` | `""` | no | +| [description](#input\_description) | Description. | `string` | `""` | no | +| [flood\_in\_encap](#input\_flood\_in\_encap) | Flood in encapsulation. | `bool` | `false` | no | +| [intra\_epg\_isolation](#input\_intra\_epg\_isolation) | Intra EPG isolation. | `bool` | `false` | no | +| [preferred\_group](#input\_preferred\_group) | Preferred group membership. | `bool` | `false` | no | +| [qos\_class](#input\_qos\_class) | QoS class. | `string` | `"unspecified"` | no | +| [custom\_qos\_policy](#input\_custom\_qos\_policy) | Custom QoS policy name. | `string` | `""` | no | +| [bridge\_domain](#input\_bridge\_domain) | Bridge domain name. | `string` | n/a | yes | +| [tags](#input\_tags) | List of EPG tags. | `list(string)` | `[]` | no | +| [trust\_control\_policy](#input\_trust\_control\_policy) | EPG Trust Control Policy Name. | `string` | `""` | no | +| [contract\_consumers](#input\_contract\_consumers) | List of contract consumers. | `list(string)` | `[]` | no | +| [contract\_providers](#input\_contract\_providers) | List of contract providers. | `list(string)` | `[]` | no | +| [contract\_imported\_consumers](#input\_contract\_imported\_consumers) | List of imported contract consumers. | `list(string)` | `[]` | no | +| [contract\_intra\_epgs](#input\_contract\_intra\_epgs) | List of intra-EPG contracts. | `list(string)` | `[]` | no | +| [contract\_masters](#input\_contract\_masters) | List of EPG contract masters. |
list(object({
endpoint_group = string
application_profile = optional(string, "")
}))
| `[]` | no | +| [physical\_domains](#input\_physical\_domains) | List of physical domains. | `list(string)` | `[]` | no | +| [subnets](#input\_subnets) | List of subnets. Default value `public`: `false`. Default value `shared`: `false`. Default value `igmp_querier`: `false`. Default value `nd_ra_prefix`: `true`. Default value `no_default_gateway`: `false`. `nlb_mode` allowed values: `mode-mcast-igmp`, `mode-uc` or `mode-mcast-static`. |
list(object({
description = optional(string, "")
ip = string
public = optional(bool, false)
shared = optional(bool, false)
igmp_querier = optional(bool, false)
nd_ra_prefix = optional(bool, true)
no_default_gateway = optional(bool, false)
nd_ra_prefix_policy = optional(string, "")
ip_pools = optional(list(object({
name = string
start_ip = optional(string, "0.0.0.0")
end_ip = optional(string, "0.0.0.0")
dns_search_suffix = optional(string, "")
dns_server = optional(string, "")
dns_suffix = optional(string, "")
wins_server = optional(string, "")
})), [])
next_hop_ip = optional(string, "")
anycast_mac = optional(string, "")
nlb_group = optional(string, "0.0.0.0")
nlb_mac = optional(string, "00:00:00:00:00:00")
nlb_mode = optional(string, "")
}))
| `[]` | no | +| [vmware\_vmm\_domains](#input\_vmware\_vmm\_domains) | List of VMware VMM domains. Default value `u_segmentation`: `false`. Default value `netflow`: `false`. Choices `deployment_immediacy`: `immediate`, `lazy`. Default value `deployment_immediacy`: `lazy`. Choices `resolution_immediacy`: `immediate`, `lazy`, `pre-provision`. Default value `resolution_immediacy`: `immediate`. Default value `allow_promiscuous`: `false`. Default value `forged_transmits`: `false`. Default value `mac_changes`: `false`. |
list(object({
name = string
deployment_immediacy = optional(string, "immediate")
netflow = optional(bool, false)
elag = optional(string, "")
active_uplinks_order = optional(string, "")
standby_uplinks = optional(string, "")
}))
| `[]` | no | +| [static\_leafs](#input\_static\_leafs) | List of static leaf switches. Allowed values `pod_id`: `1` - `255`. Default value `pod_id`: `1`. Allowed values `node_id`: `1` - `4000`. Allowed values `vlan`: `1` - `4096`. Choices `mode`: `regular`, `native`, `untagged`. Default value `mode`: `regular`. Choices `deployment_immediacy`: `immediate`, `lazy`. Default value `deployment_immediacy`: `immediate` |
list(object({
pod_id = optional(number, 1)
node_id = number
}))
| `[]` | no | +| [match\_type](#input\_match\_type) | Match type for IP type uSeg Attributes | `string` | `"any"` | no | +| [ip\_statements](#input\_ip\_statements) | IP Statements for IP type uSeg Attributes |
list(object({
name = string
use_epg_subnet = bool
ip = optional(string, "")
}))
| `[]` | no | +| [mac\_statements](#input\_mac\_statements) | MAC Statements for MAC type uSeg Attributes |
list(object({
name = string
mac = string
}))
| `[]` | no | +| [l4l7\_address\_pools](#input\_l4l7\_address\_pools) | List of EPG L4/L7 Address Pools. |
list(object({
name = string
gateway_address = string
from = optional(string, "")
to = optional(string, "")
}))
| `[]` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [dn](#output\_dn) | Distinguished name of uSeg `fvAEPg` object. | +| [name](#output\_name) | uSeg Endpoint group name. | + +## Resources + +| Name | Type | +|------|------| +| [aci_rest_managed.fvAEPg](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.fvAEPgLagPolAtt](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.fvCepNetCfgPol](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.fvCrtrn](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.fvEpAnycast](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.fvEpNlb](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.fvEpReachability](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.fvIpAttr](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.fvMacAttr](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.fvRsBd](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.fvRsCons](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.fvRsConsIf](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.fvRsCustQosPol](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.fvRsDomAtt](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.fvRsDomAtt_vmm](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.fvRsIntraEpg](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.fvRsNdPfxPol](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.fvRsNodeAtt](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.fvRsProv](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.fvRsSecInherited](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.fvRsTrustCtrl](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.fvRsVmmVSwitchEnhancedLagPol](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.fvSubnet](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.fvUplinkOrderCont](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.fvnsUcastAddrBlk](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.ipNexthopEpP](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.tagInst](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.vnsAddrInst](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | + \ No newline at end of file diff --git a/modules/terraform-aci-useg-endpoint-group/examples/complete/.terraform-docs.yml b/modules/terraform-aci-useg-endpoint-group/examples/complete/.terraform-docs.yml new file mode 100644 index 00000000..49a61a2d --- /dev/null +++ b/modules/terraform-aci-useg-endpoint-group/examples/complete/.terraform-docs.yml @@ -0,0 +1,24 @@ +version: '>= 0.14.0' + +formatter: markdown table + +content: |- + # uSeg Endpoint Group Example + + To run this example you need to execute: + + ```bash + $ terraform init + $ terraform plan + $ terraform apply + ``` + + Note that this example will create resources. Resources can be destroyed with `terraform destroy`. + + ```hcl + {{ include "./main.tf" }} + ``` + +output: + file: README.md + mode: replace diff --git a/modules/terraform-aci-useg-endpoint-group/examples/complete/README.md b/modules/terraform-aci-useg-endpoint-group/examples/complete/README.md new file mode 100644 index 00000000..a6165b70 --- /dev/null +++ b/modules/terraform-aci-useg-endpoint-group/examples/complete/README.md @@ -0,0 +1,86 @@ + +# uSeg Endpoint Group Example + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example will create resources. Resources can be destroyed with `terraform destroy`. + +```hcl +module "aci_useg_endpoint_group" { + source = "netascode/nac-aci/aci//modules/terraform-aci-endpoint-group" + version = ">= 0.8.0" + + tenant = "ABC" + application_profile = "AP1" + name = "uSeg_EPG1" + alias = "uSeg-EPG1-ALIAS" + description = "My uSeg EPG Description" + flood_in_encap = false + intra_epg_isolation = true + preferred_group = true + qos_class = "level1" + custom_qos_policy = "CQP1" + bridge_domain = "BD1" + trust_control_policy = "TRUST_POL" + contract_consumers = ["CON1"] + contract_providers = ["CON1"] + contract_imported_consumers = ["I_CON1"] + contract_intra_epgs = ["CON1"] + contract_masters = [{ + endpoint_group = "EPG2" + application_profile = "AP1" + }] + physical_domains = ["PHY1"] + tags = [ + "tag1", + "tag2" + ] + + match_type = "any" + ip_statements = [{ + name = "ip_1" + use_epg_subnet = false + ip = "1.2.2.11" + }, { + name = "ip_2" + use_epg_subnet = true + }] + mac_statements = [{ + name = "mac_1" + mac = "02:aa:68:22:58:d1" + }, { + name = "mac_2" + mac = "02:aa:68:22:58:d2" + }] + subnets = [{ + description = "Subnet Description" + ip = "1.2.2.1/24" + public = true + shared = true + igmp_querier = true + nd_ra_prefix = true + no_default_gateway = false + }] + vmware_vmm_domains = [{ + name = "VMW1" + netflow = false + deployment_immediacy = "immediate" + }] + static_leafs = [{ + node_id = 102 + }] + l4l7_address_pools = [{ + name = "POOL1" + gateway_address = "1.2.2.1/24" + from = "1.2.2.10" + to = "1.2.2.100" + }] +} +``` + \ No newline at end of file diff --git a/modules/terraform-aci-useg-endpoint-group/examples/complete/main.tf b/modules/terraform-aci-useg-endpoint-group/examples/complete/main.tf new file mode 100644 index 00000000..7172434c --- /dev/null +++ b/modules/terraform-aci-useg-endpoint-group/examples/complete/main.tf @@ -0,0 +1,70 @@ +module "aci_useg_endpoint_group" { + source = "netascode/nac-aci/aci//modules/terraform-aci-endpoint-group" + version = ">= 0.8.0" + + tenant = "ABC" + application_profile = "AP1" + name = "uSeg_EPG1" + alias = "uSeg-EPG1-ALIAS" + description = "My uSeg EPG Description" + flood_in_encap = false + intra_epg_isolation = true + preferred_group = true + qos_class = "level1" + custom_qos_policy = "CQP1" + bridge_domain = "BD1" + trust_control_policy = "TRUST_POL" + contract_consumers = ["CON1"] + contract_providers = ["CON1"] + contract_imported_consumers = ["I_CON1"] + contract_intra_epgs = ["CON1"] + contract_masters = [{ + endpoint_group = "EPG2" + application_profile = "AP1" + }] + physical_domains = ["PHY1"] + tags = [ + "tag1", + "tag2" + ] + + match_type = "any" + ip_statements = [{ + name = "ip_1" + use_epg_subnet = false + ip = "1.2.2.11" + }, { + name = "ip_2" + use_epg_subnet = true + }] + mac_statements = [{ + name = "mac_1" + mac = "02:aa:68:22:58:d1" + }, { + name = "mac_2" + mac = "02:aa:68:22:58:d2" + }] + subnets = [{ + description = "Subnet Description" + ip = "1.2.2.1/24" + public = true + shared = true + igmp_querier = true + nd_ra_prefix = true + no_default_gateway = false + }] + vmware_vmm_domains = [{ + name = "VMW1" + netflow = false + deployment_immediacy = "immediate" + }] + static_leafs = [{ + node_id = 102 + }] + l4l7_address_pools = [{ + name = "POOL1" + gateway_address = "1.2.2.1/24" + from = "1.2.2.10" + to = "1.2.2.100" + }] +} diff --git a/modules/terraform-aci-useg-endpoint-group/examples/complete/versions.tf b/modules/terraform-aci-useg-endpoint-group/examples/complete/versions.tf new file mode 100644 index 00000000..9299fb61 --- /dev/null +++ b/modules/terraform-aci-useg-endpoint-group/examples/complete/versions.tf @@ -0,0 +1,11 @@ + +terraform { + required_version = ">= 1.3.0" + + required_providers { + aci = { + source = "CiscoDevNet/aci" + version = ">= 2.0.0" + } + } +} diff --git a/modules/terraform-aci-useg-endpoint-group/main.tf b/modules/terraform-aci-useg-endpoint-group/main.tf new file mode 100644 index 00000000..b0ead883 --- /dev/null +++ b/modules/terraform-aci-useg-endpoint-group/main.tf @@ -0,0 +1,300 @@ +locals { + ip_pools_list = flatten([ + for subnet in var.subnets : [ + for pool in lookup(subnet, "ip_pools", []) : { + id = "${subnet.ip}-${pool.name}" + subnet_ip = subnet.ip + name = pool.name + start_ip = pool.start_ip + end_ip = pool.end_ip + dns_search_suffix = pool.dns_search_suffix + dns_server = pool.dns_server + dns_suffix = pool.dns_suffix + wins_server = pool.wins_server + } + ] + ]) +} + +resource "aci_rest_managed" "fvAEPg" { + dn = "uni/tn-${var.tenant}/ap-${var.application_profile}/epg-${var.name}" + class_name = "fvAEPg" + content = { + name = var.name + nameAlias = var.alias + descr = var.description + isAttrBasedEPg = "yes" + floodOnEncap = var.flood_in_encap == true ? "enabled" : "disabled" + pcEnfPref = var.intra_epg_isolation == true ? "enforced" : "unenforced" + prefGrMemb = var.preferred_group == true ? "include" : "exclude" + prio = var.qos_class + } +} + +resource "aci_rest_managed" "fvRsBd" { + dn = "${aci_rest_managed.fvAEPg.dn}/rsbd" + class_name = "fvRsBd" + content = { + tnFvBDName = var.bridge_domain + } +} + +resource "aci_rest_managed" "fvRsCustQosPol" { + count = var.custom_qos_policy != "" ? 1 : 0 + dn = "${aci_rest_managed.fvAEPg.dn}/rscustQosPol" + class_name = "fvRsCustQosPol" + content = { + tnQosCustomPolName = var.custom_qos_policy + } +} + +resource "aci_rest_managed" "fvCrtrn" { + dn = "${aci_rest_managed.fvAEPg.dn}/crtrn" + class_name = "fvCrtrn" + content = { + match = var.match_type + } +} + +resource "aci_rest_managed" "fvIpAttr" { + for_each = { for ip_statement in var.ip_statements : ip_statement.name => ip_statement } + + dn = "${aci_rest_managed.fvAEPg.dn}/crtrn/ipattr-${each.value.name}" + class_name = "fvIpAttr" + content = { + name = each.value.name + usefvSubnet = each.value.use_epg_subnet ? "yes" : "no" + ip = each.value.use_epg_subnet ? "0.0.0.0" : each.value.ip + } + + depends_on = [ + aci_rest_managed.fvCrtrn + ] +} + +resource "aci_rest_managed" "fvMacAttr" { + for_each = { for mac_statement in var.mac_statements : mac_statement.name => mac_statement } + + dn = "${aci_rest_managed.fvAEPg.dn}/crtrn/macattr-${each.value.name}" + class_name = "fvMacAttr" + content = { + name = each.value.name + mac = each.value.mac + } + + depends_on = [ + aci_rest_managed.fvCrtrn + ] +} + +resource "aci_rest_managed" "fvSubnet" { + for_each = { for subnet in var.subnets : subnet.ip => subnet } + dn = "${aci_rest_managed.fvAEPg.dn}/subnet-[${each.value.ip}]" + class_name = "fvSubnet" + content = { + ip = each.value.ip + descr = each.value.description != null ? each.value.description : "" + ctrl = join(",", concat(each.value.nd_ra_prefix == true ? ["nd"] : [], each.value.no_default_gateway == true ? ["no-default-gateway"] : [], each.value.igmp_querier == true ? ["querier"] : [])) + scope = join(",", concat(each.value.public == true ? ["public"] : ["private"], each.value.shared == true ? ["shared"] : [])) + } +} + +resource "aci_rest_managed" "fvRsNdPfxPol" { + for_each = { for subnet in var.subnets : subnet.ip => subnet if subnet.nd_ra_prefix_policy != "" } + dn = "${aci_rest_managed.fvSubnet[each.key].dn}/rsNdPfxPol" + class_name = "fvRsNdPfxPol" + content = { + tnNdPfxPolName = each.value.nd_ra_prefix_policy + } +} + +resource "aci_rest_managed" "fvCepNetCfgPol" { + for_each = { for pool in local.ip_pools_list : pool.id => pool } + dn = "${aci_rest_managed.fvSubnet[each.value.subnet_ip].dn}/cepNetCfgPol-${each.value.name}" + class_name = "fvCepNetCfgPol" + content = { + name = each.value.name + startIp = each.value.start_ip + endIp = each.value.end_ip + dnsSearchSuffix = each.value.dns_search_suffix + dnsServers = each.value.dns_server + dnsSuffix = each.value.dns_suffix + winsServers = each.value.wins_server + } +} + +resource "aci_rest_managed" "fvEpReachability" { + for_each = { for subnet in var.subnets : subnet.ip => subnet if subnet.next_hop_ip != "" } + dn = "${aci_rest_managed.fvSubnet[each.value.ip].dn}/epReach" + class_name = "fvEpReachability" +} + +resource "aci_rest_managed" "ipNexthopEpP" { + for_each = { for subnet in var.subnets : subnet.ip => subnet if subnet.next_hop_ip != "" } + dn = "${aci_rest_managed.fvEpReachability[each.value.ip].dn}/nh-[${each.value.next_hop_ip}]" + class_name = "ipNexthopEpP" + content = { + nhAddr = each.value.next_hop_ip + } +} + +resource "aci_rest_managed" "fvEpAnycast" { + for_each = { for subnet in var.subnets : subnet.ip => subnet if subnet.anycast_mac != "" } + dn = "${aci_rest_managed.fvSubnet[each.value.ip].dn}/epAnycast-${each.value.anycast_mac}" + class_name = "fvEpAnycast" + content = { + mac = each.value.anycast_mac + } +} + +resource "aci_rest_managed" "fvEpNlb" { + for_each = { for subnet in var.subnets : subnet.ip => subnet if subnet.nlb_mode != "" } + dn = "${aci_rest_managed.fvSubnet[each.value.ip].dn}/epnlb" + class_name = "fvEpNlb" + content = { + group = each.value.nlb_group + mac = each.value.nlb_mac + mode = each.value.nlb_mode == "mode-mcast-static" ? "mode-mcast--static" : each.value.nlb_mode + } +} + +resource "aci_rest_managed" "tagInst" { + for_each = toset(var.tags) + dn = "${aci_rest_managed.fvAEPg.dn}/tag-${each.value}" + class_name = "tagInst" + content = { + name = each.value + } +} + +resource "aci_rest_managed" "fvRsTrustCtrl" { + count = var.trust_control_policy != "" ? 1 : 0 + dn = "${aci_rest_managed.fvAEPg.dn}/rstrustCtrl" + class_name = "fvRsTrustCtrl" + content = { + tnFhsTrustCtrlPolName = var.trust_control_policy + } +} + +resource "aci_rest_managed" "fvRsCons" { + for_each = toset(var.contract_consumers) + dn = "${aci_rest_managed.fvAEPg.dn}/rscons-${each.value}" + class_name = "fvRsCons" + content = { + tnVzBrCPName = each.value + } +} + +resource "aci_rest_managed" "fvRsProv" { + for_each = toset(var.contract_providers) + dn = "${aci_rest_managed.fvAEPg.dn}/rsprov-${each.value}" + class_name = "fvRsProv" + content = { + tnVzBrCPName = each.value + } +} + +resource "aci_rest_managed" "fvRsConsIf" { + for_each = toset(var.contract_imported_consumers) + dn = "${aci_rest_managed.fvAEPg.dn}/rsconsIf-${each.value}" + class_name = "fvRsConsIf" + content = { + tnVzCPIfName = each.value + } +} + +resource "aci_rest_managed" "fvRsIntraEpg" { + for_each = toset(var.contract_intra_epgs) + dn = "${aci_rest_managed.fvAEPg.dn}/rsintraEpg-${each.value}" + class_name = "fvRsIntraEpg" + content = { + tnVzBrCPName = each.value + } +} + +resource "aci_rest_managed" "fvRsSecInherited" { + for_each = { for master in var.contract_masters : master.endpoint_group => master } + dn = "${aci_rest_managed.fvAEPg.dn}/rssecInherited-[uni/tn-${var.tenant}/ap-${try(each.value.application_profile, var.application_profile)}/epg-${each.value.endpoint_group}]" + class_name = "fvRsSecInherited" + content = { + tDn = "uni/tn-${var.tenant}/ap-${try(each.value.application_profile, var.application_profile)}/epg-${each.value.endpoint_group}" + } +} + +resource "aci_rest_managed" "fvRsDomAtt" { + for_each = toset(var.physical_domains) + dn = "${aci_rest_managed.fvAEPg.dn}/rsdomAtt-[uni/phys-${each.value}]" + class_name = "fvRsDomAtt" + content = { + tDn = "uni/phys-${each.value}" + } +} + +resource "aci_rest_managed" "fvRsNodeAtt" { + for_each = { for sp in var.static_leafs : "${sp.node_id}" => sp } + dn = "${aci_rest_managed.fvAEPg.dn}/rsnodeAtt-[${format("topology/pod-%s/node-%s", each.value.pod_id, each.value.node_id)}]" + class_name = "fvRsNodeAtt" + content = { + tDn = format("topology/pod-%s/node-%s", each.value.pod_id, each.value.node_id) + instrImedcy = "immediate" + } +} + +resource "aci_rest_managed" "fvRsDomAtt_vmm" { + for_each = { for vmm_vwm in var.vmware_vmm_domains : vmm_vwm.name => vmm_vwm } + dn = "${aci_rest_managed.fvAEPg.dn}/rsdomAtt-[uni/vmmp-VMware/dom-${each.value.name}]" + class_name = "fvRsDomAtt" + content = { + tDn = "uni/vmmp-VMware/dom-${each.value.name}" + instrImedcy = each.value.deployment_immediacy + netflowPref = each.value.netflow == true ? "enabled" : "disabled" + switchingMode = "native" + } +} + +resource "aci_rest_managed" "fvUplinkOrderCont" { + for_each = { for vmm_vwm in var.vmware_vmm_domains : vmm_vwm.name => vmm_vwm if vmm_vwm.active_uplinks_order != "" || vmm_vwm.standby_uplinks != "" } + dn = "${aci_rest_managed.fvRsDomAtt_vmm[each.key].dn}/uplinkorder" + class_name = "fvUplinkOrderCont" + + content = { + active = each.value.active_uplinks_order + standby = each.value.standby_uplinks + } +} + +resource "aci_rest_managed" "fvAEPgLagPolAtt" { + for_each = { for vmm_vwm in var.vmware_vmm_domains : vmm_vwm.name => vmm_vwm if vmm_vwm.elag != "" } + dn = "${aci_rest_managed.fvRsDomAtt_vmm[each.key].dn}/epglagpolatt" + class_name = "fvAEPgLagPolAtt" +} + +resource "aci_rest_managed" "fvRsVmmVSwitchEnhancedLagPol" { + for_each = { for vmm_vwm in var.vmware_vmm_domains : vmm_vwm.name => vmm_vwm if vmm_vwm.elag != "" } + dn = "${aci_rest_managed.fvAEPgLagPolAtt[each.key].dn}/rsvmmVSwitchEnhancedLagPol" + class_name = "fvRsVmmVSwitchEnhancedLagPol" + content = { + tDn = "uni/vmmp-VMware/dom-${each.value.name}/vswitchpolcont/enlacplagp-${each.value.elag}" + } +} + +resource "aci_rest_managed" "vnsAddrInst" { + for_each = { for pool in var.l4l7_address_pools : pool.name => pool } + dn = "${aci_rest_managed.fvAEPg.dn}/CtrlrAddrInst-${each.key}" + class_name = "vnsAddrInst" + content = { + name = each.value.name + addr = each.value.gateway_address + } +} + +resource "aci_rest_managed" "fvnsUcastAddrBlk" { + for_each = { for pool in var.l4l7_address_pools : pool.name => pool if pool.from != "" && pool.to != "" } + dn = "${aci_rest_managed.vnsAddrInst[each.key].dn}/fromaddr-[${each.value.from}]-toaddr-[${each.value.to}]" + class_name = "fvnsUcastAddrBlk" + content = { + from = each.value.from + to = each.value.to + } +} + diff --git a/modules/terraform-aci-useg-endpoint-group/outputs.tf b/modules/terraform-aci-useg-endpoint-group/outputs.tf new file mode 100644 index 00000000..e40ed287 --- /dev/null +++ b/modules/terraform-aci-useg-endpoint-group/outputs.tf @@ -0,0 +1,9 @@ +output "dn" { + value = aci_rest_managed.fvAEPg.id + description = "Distinguished name of uSeg `fvAEPg` object." +} + +output "name" { + value = aci_rest_managed.fvAEPg.content.name + description = "uSeg Endpoint group name." +} diff --git a/modules/terraform-aci-useg-endpoint-group/variables.tf b/modules/terraform-aci-useg-endpoint-group/variables.tf new file mode 100644 index 00000000..7716d607 --- /dev/null +++ b/modules/terraform-aci-useg-endpoint-group/variables.tf @@ -0,0 +1,397 @@ +variable "tenant" { + description = "Tenant name." + type = string + + validation { + condition = can(regex("^[a-zA-Z0-9_.:-]{0,64}$", var.tenant)) + error_message = "Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." + } +} + +variable "application_profile" { + description = "Application profile name." + type = string + + validation { + condition = can(regex("^[a-zA-Z0-9_.:-]{0,64}$", var.application_profile)) + error_message = "Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." + } +} + +variable "name" { + description = "uSeg Endpoint group name." + type = string + + validation { + condition = can(regex("^[a-zA-Z0-9_.:-]{0,64}$", var.name)) + error_message = "Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." + } +} + +variable "alias" { + description = "Alias." + type = string + default = "" + + validation { + condition = can(regex("^[a-zA-Z0-9_.:-]{0,64}$", var.alias)) + error_message = "Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." + } +} + +variable "description" { + description = "Description." + type = string + default = "" + + validation { + condition = can(regex("^[a-zA-Z0-9\\!#$%()*,-./:;@ _{|}~?&+]{0,128}$", var.description)) + error_message = "Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `\\`, `!`, `#`, `$`, `%`, `(`, `)`, `*`, `,`, `-`, `.`, `/`, `:`, `;`, `@`, ` `, `_`, `{`, `|`, }`, `~`, `?`, `&`, `+`. Maximum characters: 128." + } +} + +variable "flood_in_encap" { + description = "Flood in encapsulation." + type = bool + default = false +} + +variable "intra_epg_isolation" { + description = "Intra EPG isolation." + type = bool + default = false +} + +variable "preferred_group" { + description = "Preferred group membership." + type = bool + default = false +} + +variable "qos_class" { + description = "QoS class." + type = string + default = "unspecified" + + validation { + condition = can(contains(["level1", "level2", "level3", "level4", "level5", "level6", "unspecified"], var.qos_class)) + error_message = "Allowed values are `level1` to `level6` and `unspecified`." + } +} + +variable "custom_qos_policy" { + description = "Custom QoS policy name." + type = string + default = "" + + validation { + condition = can(regex("^[a-zA-Z0-9_.:-]{0,64}$", var.custom_qos_policy)) + error_message = "Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." + } +} + +variable "bridge_domain" { + description = "Bridge domain name." + type = string + + validation { + condition = can(regex("^[a-zA-Z0-9_.:-]{0,64}$", var.bridge_domain)) + error_message = "Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." + } +} + +variable "tags" { + description = "List of EPG tags." + type = list(string) + default = [] + + validation { + condition = alltrue([ + for tag in var.tags : can(regex("^[a-zA-Z0-9_.-]{0,64}$", tag)) + ]) + error_message = "Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `-`. Maximum characters: 64." + } +} + +variable "trust_control_policy" { + description = "EPG Trust Control Policy Name." + type = string + default = "" + + validation { + condition = can(regex("^[a-zA-Z0-9_.:-]{0,64}$", var.trust_control_policy)) + error_message = "Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." + } +} + +variable "contract_consumers" { + description = "List of contract consumers." + type = list(string) + default = [] + + validation { + condition = alltrue([ + for c in var.contract_consumers : can(regex("^[a-zA-Z0-9_.:-]{0,64}$", c)) + ]) + error_message = "Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." + } +} + +variable "contract_providers" { + description = "List of contract providers." + type = list(string) + default = [] + + validation { + condition = alltrue([ + for c in var.contract_providers : can(regex("^[a-zA-Z0-9_.:-]{0,64}$", c)) + ]) + error_message = "Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." + } +} + +variable "contract_imported_consumers" { + description = "List of imported contract consumers." + type = list(string) + default = [] + + validation { + condition = alltrue([ + for c in var.contract_imported_consumers : can(regex("^[a-zA-Z0-9_.:-]{0,64}$", c)) + ]) + error_message = "Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." + } +} + +variable "contract_intra_epgs" { + description = "List of intra-EPG contracts." + type = list(string) + default = [] + + validation { + condition = alltrue([ + for c in var.contract_intra_epgs : can(regex("^[a-zA-Z0-9_.:-]{0,64}$", c)) + ]) + error_message = "Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." + } +} + +variable "contract_masters" { + description = "List of EPG contract masters." + type = list(object({ + endpoint_group = string + application_profile = optional(string, "") + })) + default = [] + + validation { + condition = alltrue([ + for master in var.contract_masters : can(regex("^[a-zA-Z0-9_.:-]{0,64}$", master.endpoint_group)) + ]) + error_message = "Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." + } + + validation { + condition = alltrue([ + for master in var.contract_masters : can(regex("^[a-zA-Z0-9_.:-]{0,64}$", master.application_profile)) + ]) + error_message = "Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." + } +} + +variable "physical_domains" { + description = "List of physical domains." + type = list(string) + default = [] + + validation { + condition = alltrue([ + for pd in var.physical_domains : can(regex("^[a-zA-Z0-9_.:-]{0,64}$", pd)) + ]) + error_message = "Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." + } +} + +variable "subnets" { + description = "List of subnets. Default value `public`: `false`. Default value `shared`: `false`. Default value `igmp_querier`: `false`. Default value `nd_ra_prefix`: `true`. Default value `no_default_gateway`: `false`. `nlb_mode` allowed values: `mode-mcast-igmp`, `mode-uc` or `mode-mcast-static`." + type = list(object({ + description = optional(string, "") + ip = string + public = optional(bool, false) + shared = optional(bool, false) + igmp_querier = optional(bool, false) + nd_ra_prefix = optional(bool, true) + no_default_gateway = optional(bool, false) + nd_ra_prefix_policy = optional(string, "") + ip_pools = optional(list(object({ + name = string + start_ip = optional(string, "0.0.0.0") + end_ip = optional(string, "0.0.0.0") + dns_search_suffix = optional(string, "") + dns_server = optional(string, "") + dns_suffix = optional(string, "") + wins_server = optional(string, "") + })), []) + next_hop_ip = optional(string, "") + anycast_mac = optional(string, "") + nlb_group = optional(string, "0.0.0.0") + nlb_mac = optional(string, "00:00:00:00:00:00") + nlb_mode = optional(string, "") + })) + default = [] + + validation { + condition = alltrue([ + for s in var.subnets : s.description == null || can(regex("^[a-zA-Z0-9\\!#$%()*,-./:;@ _{|}~?&+]{0,128}$", s.description)) + ]) + error_message = "`description`: Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `\\`, `!`, `#`, `$`, `%`, `(`, `)`, `*`, `,`, `-`, `.`, `/`, `:`, `;`, `@`, ` `, `_`, `{`, `|`, }`, `~`, `?`, `&`, `+`. Maximum characters: 128." + } + + validation { + condition = alltrue([ + for s in var.subnets : s.nd_ra_prefix_policy == null || can(regex("^[a-zA-Z0-9_.:-]{0,64}$", s.nd_ra_prefix_policy)) + ]) + error_message = "`nd_ra_prefix_policy`: Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." + } + + validation { + condition = alltrue([ + for s in var.subnets : try(contains(["mode-mcast-igmp", "mode-uc", "mode-mcast-static", ""], s.nlb_mode), false) + ]) + error_message = "`nlb_mode`: Allowed values: `mode-mcast-igmp`, `mode-uc` or `mode-mcast-static`." + } +} + +variable "vmware_vmm_domains" { + description = "List of VMware VMM domains. Default value `u_segmentation`: `false`. Default value `netflow`: `false`. Choices `deployment_immediacy`: `immediate`, `lazy`. Default value `deployment_immediacy`: `lazy`. Choices `resolution_immediacy`: `immediate`, `lazy`, `pre-provision`. Default value `resolution_immediacy`: `immediate`. Default value `allow_promiscuous`: `false`. Default value `forged_transmits`: `false`. Default value `mac_changes`: `false`." + type = list(object({ + name = string + deployment_immediacy = optional(string, "immediate") + netflow = optional(bool, false) + elag = optional(string, "") + active_uplinks_order = optional(string, "") + standby_uplinks = optional(string, "") + })) + default = [] + + validation { + condition = alltrue([ + for dom in var.vmware_vmm_domains : can(regex("^[a-zA-Z0-9_.:-]{0,64}$", dom.name)) + ]) + error_message = "`name`: Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." + } + + validation { + condition = alltrue([ + for dom in var.vmware_vmm_domains : dom.deployment_immediacy == null || try(contains(["immediate", "lazy"], dom.deployment_immediacy), false) + ]) + error_message = "`deployment_immediacy`: Allowed values are `immediate` or `lazy`." + } + + validation { + condition = alltrue([ + for dom in var.vmware_vmm_domains : can(regex("^[a-zA-Z0-9_.:-]{0,64}$", dom.elag)) + ]) + error_message = "`elag`: Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." + } +} + +variable "static_leafs" { + description = "List of static leaf switches. Allowed values `pod_id`: `1` - `255`. Default value `pod_id`: `1`. Allowed values `node_id`: `1` - `4000`. Allowed values `vlan`: `1` - `4096`. Choices `mode`: `regular`, `native`, `untagged`. Default value `mode`: `regular`. Choices `deployment_immediacy`: `immediate`, `lazy`. Default value `deployment_immediacy`: `immediate`" + type = list(object({ + pod_id = optional(number, 1) + node_id = number + })) + default = [] + + + validation { + condition = alltrue([ + for sp in var.static_leafs : sp.pod_id == null || try(sp.pod_id >= 1 && sp.pod_id <= 255, false) + ]) + error_message = "`pod_id`: Minimum value: `1`. Maximum value: `255`." + } + + validation { + condition = alltrue([ + for sp in var.static_leafs : (sp.node_id >= 1 && sp.node_id <= 4000) + ]) + error_message = "`node_id`: Minimum value: `1`. Maximum value: `4000`." + } +} + +variable "match_type" { + description = "Match type for IP type uSeg Attributes" + type = string + default = "any" + + validation { + condition = can(contains(["any", "all"], var.match_type)) + error_message = "`match_type`: Allowed values are `any` or `all`." + } +} + +variable "ip_statements" { + description = "IP Statements for IP type uSeg Attributes" + type = list(object({ + name = string + use_epg_subnet = bool + ip = optional(string, "") + })) + default = [] + + validation { + condition = alltrue([ + for ip_statement in var.ip_statements : can(regex("^[a-zA-Z0-9_.:-]{0,64}$", ip_statement.name)) + ]) + error_message = "`name`: Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." + } + + validation { + condition = alltrue([ + for ip_statement in var.ip_statements : (ip_statement.use_epg_subnet) || (!ip_statement.use_epg_subnet && (ip_statement.ip != "" && ip_statement.ip != "0.0.0.0")) + ]) + error_message = "`ip`: A valid IP or subnet must be specified when `use_epg_subnet` is `false`." + } +} + +variable "mac_statements" { + description = "MAC Statements for MAC type uSeg Attributes" + type = list(object({ + name = string + mac = string + })) + default = [] + + validation { + condition = alltrue([ + for mac_statement in var.mac_statements : can(regex("^[a-zA-Z0-9_.:-]{0,64}$", mac_statement.name)) + ]) + error_message = "`name`: Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." + } + + validation { + condition = alltrue([ + for mac_statement in var.mac_statements : can(regex("^([0-9A-Fa-f]{2}[:]){5}([0-9A-Fa-f]{2})$", mac_statement.mac)) + ]) + error_message = "`mac`: Valid MAC Format: `XX:XX:XX:XX:XX:XX`." + } +} + +variable "l4l7_address_pools" { + description = "List of EPG L4/L7 Address Pools." + type = list(object({ + name = string + gateway_address = string + from = optional(string, "") + to = optional(string, "") + })) + default = [] + + validation { + condition = alltrue([ + for pool in var.l4l7_address_pools : can(regex("^[a-zA-Z0-9_.:-]{0,64}$", pool.name)) + ]) + error_message = "`name`: Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." + } +} diff --git a/modules/terraform-aci-useg-endpoint-group/versions.tf b/modules/terraform-aci-useg-endpoint-group/versions.tf new file mode 100644 index 00000000..9299fb61 --- /dev/null +++ b/modules/terraform-aci-useg-endpoint-group/versions.tf @@ -0,0 +1,11 @@ + +terraform { + required_version = ">= 1.3.0" + + required_providers { + aci = { + source = "CiscoDevNet/aci" + version = ">= 2.0.0" + } + } +} From 290852ca5837596c2a7d107d3de695cca7f16f00 Mon Sep 17 00:00:00 2001 From: Andrew Byrne Date: Thu, 23 May 2024 18:44:41 +1000 Subject: [PATCH 2/2] Fix tflint error --- modules/terraform-aci-useg-endpoint-group/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/terraform-aci-useg-endpoint-group/main.tf b/modules/terraform-aci-useg-endpoint-group/main.tf index b0ead883..4ed931b1 100644 --- a/modules/terraform-aci-useg-endpoint-group/main.tf +++ b/modules/terraform-aci-useg-endpoint-group/main.tf @@ -231,7 +231,7 @@ resource "aci_rest_managed" "fvRsDomAtt" { } resource "aci_rest_managed" "fvRsNodeAtt" { - for_each = { for sp in var.static_leafs : "${sp.node_id}" => sp } + for_each = { for sp in var.static_leafs : sp.node_id => sp } dn = "${aci_rest_managed.fvAEPg.dn}/rsnodeAtt-[${format("topology/pod-%s/node-%s", each.value.pod_id, each.value.node_id)}]" class_name = "fvRsNodeAtt" content = {