From f1b2b3f58d0eea590dadc196285c2e68e7799ad1 Mon Sep 17 00:00:00 2001 From: Rob van der Kind Date: Wed, 17 Jul 2024 09:58:57 +0200 Subject: [PATCH] adding support for unidir contracts including feedback --- aci_tenants.tf | 33 ++++- defaults/defaults.yaml | 1 + modules/terraform-aci-contract/README.md | 8 +- modules/terraform-aci-contract/main.tf | 97 ++++++++++++++- modules/terraform-aci-contract/variables.tf | 131 ++++++++++++++++++-- 5 files changed, 251 insertions(+), 19 deletions(-) diff --git a/aci_tenants.tf b/aci_tenants.tf index c40bb59f..87d6e393 100644 --- a/aci_tenants.tf +++ b/aci_tenants.tf @@ -1730,12 +1730,13 @@ locals { qos_class = try(contract.qos_class, local.defaults.apic.tenants.contracts.qos_class) target_dscp = try(contract.target_dscp, local.defaults.apic.tenants.contracts.target_dscp) subjects = [for subject in try(contract.subjects, []) : { - name = "${subject.name}${local.defaults.apic.tenants.contracts.subjects.name_suffix}" - alias = try(subject.alias, "") - description = try(subject.description, "") - service_graph = try("${subject.service_graph}${local.defaults.apic.tenants.services.service_graph_templates.name_suffix}", null) - qos_class = try(subject.qos_class, local.defaults.apic.tenants.contracts.subjects.qos_class) - target_dscp = try(subject.target_dscp, local.defaults.apic.tenants.contracts.subjects.target_dscp) + name = "${subject.name}${local.defaults.apic.tenants.contracts.subjects.name_suffix}" + alias = try(subject.alias, "") + description = try(subject.description, "") + reverse_filter_ports = try(subject.reverse_filter_ports, local.defaults.apic.tenants.contracts.subjects.reverse_filter_ports) + service_graph = try("${subject.service_graph}${local.defaults.apic.tenants.services.service_graph_templates.name_suffix}", null) + qos_class = try(subject.qos_class, local.defaults.apic.tenants.contracts.subjects.qos_class) + target_dscp = try(subject.target_dscp, local.defaults.apic.tenants.contracts.subjects.target_dscp) filters = [for filter in try(subject.filters, []) : { filter = "${filter.filter}${local.defaults.apic.tenants.filters.name_suffix}" action = try(filter.action, local.defaults.apic.tenants.contracts.subjects.filters.action) @@ -1743,6 +1744,26 @@ locals { log = try(filter.log, local.defaults.apic.tenants.contracts.subjects.filters.log) no_stats = try(filter.no_stats, local.defaults.apic.tenants.contracts.subjects.filters.no_stats) }] + consumer_to_provider_service_graph = try("${subject.consumer_to_provider.service_graph}${local.defaults.apic.tenants.services.service_graph_templates.name_suffix}", null) + consumer_to_provider_qos_class = try(subject.consumer_to_provider.qos_class, local.defaults.apic.tenants.contracts.subjects.qos_class) + consumer_to_provider_target_dscp = try(subject.consumer_to_provider.target_dscp, local.defaults.apic.tenants.contracts.subjects.target_dscp) + consumer_to_provider_filters = [for filter in try(subject.consumer_to_provider.filters, []) : { + filter = "${filter.filter}${local.defaults.apic.tenants.filters.name_suffix}" + action = try(filter.action, local.defaults.apic.tenants.contracts.subjects.filters.action) + priority = try(filter.priority, local.defaults.apic.tenants.contracts.subjects.filters.priority) + log = try(filter.log, local.defaults.apic.tenants.contracts.subjects.filters.log) + no_stats = try(filter.no_stats, local.defaults.apic.tenants.contracts.subjects.filters.no_stats) + }] + provider_to_consumer_service_graph = try("${subject.provider_to_consumer.service_graph}${local.defaults.apic.tenants.services.service_graph_templates.name_suffix}", null) + provider_to_consumer_qos_class = try(subject.provider_to_consumer.qos_class, local.defaults.apic.tenants.contracts.subjects.qos_class) + provider_to_consumer_target_dscp = try(subject.provider_to_consumer.target_dscp, local.defaults.apic.tenants.contracts.subjects.target_dscp) + provider_to_consumer_filters = [for filter in try(subject.provider_to_consumer.filters, []) : { + filter = "${filter.filter}${local.defaults.apic.tenants.filters.name_suffix}" + action = try(filter.action, local.defaults.apic.tenants.contracts.subjects.filters.action) + priority = try(filter.priority, local.defaults.apic.tenants.contracts.subjects.filters.priority) + log = try(filter.log, local.defaults.apic.tenants.contracts.subjects.filters.log) + no_stats = try(filter.no_stats, local.defaults.apic.tenants.contracts.subjects.filters.no_stats) + }] }] } ] diff --git a/defaults/defaults.yaml b/defaults/defaults.yaml index 8df1fedd..ea83e311 100644 --- a/defaults/defaults.yaml +++ b/defaults/defaults.yaml @@ -1107,6 +1107,7 @@ defaults: name_suffix: "" qos_class: unspecified target_dscp: unspecified + reverse_filter_ports: true filters: action: permit priority: default diff --git a/modules/terraform-aci-contract/README.md b/modules/terraform-aci-contract/README.md index 0977817c..f9e429eb 100644 --- a/modules/terraform-aci-contract/README.md +++ b/modules/terraform-aci-contract/README.md @@ -62,7 +62,7 @@ module "aci_contract" { | [scope](#input\_scope) | Contract scope. Choices: `application-profile`, `tenant`, `context`, `global`. | `string` | `"context"` | no | | [qos\_class](#input\_qos\_class) | Contract QoS Class. Choices: `unspecified`, `level1`, `level2`, `level3`, `level4`, `level5`, `level6`. | `string` | `"unspecified"` | no | | [target\_dscp](#input\_target\_dscp) | Contract Target DSCP. Valid values are `unspecified`, `CS0`, `CS1`, `AF11`, `AF12`, `AF13`, `CS2`, `AF21`, `AF22`, `AF23`, `CS4`, `AF41`, `AF42`, `AF43`, `CS5`, `VA`, `EF`, `CS6`, `CS7` or a number between 0 and 63. | `string` | `"unspecified"` | no | -| [subjects](#input\_subjects) | List of contract subjects. Choices `action`: `permit`, `deny`. Default value `action`: `permit`. Choices `priority`: `default`, `level1`, `level2`, `level3`. Default value `priority`: `default`. Default value `log`: `false`. Default value `no_stats`: `false`. Choices `qos_class`: `unspecified`, `level1`, `level2`, `level3`, `level4`, `level5` or`level6`. Default value `qos_class`: `unspecified`. Choices `dscp_target` : `unspecified`, `CS0`, `CS1`, `AF11`, `AF12`, `AF13`, `CS2`, `AF21`, `AF22`, `AF23`, `CS4`, `AF41`, `AF42`, `AF43`, `CS5`, `VA`, `EF`, `CS6` `CS7` or a number between 0 and 63. Default value `dscp_target`: `unspecified` |
list(object({
name = string
alias = optional(string, "")
description = optional(string, "")
service_graph = optional(string)
qos_class = optional(string, "unspecified")
target_dscp = optional(string, "unspecified")
filters = optional(list(object({
filter = string
action = optional(string, "permit")
priority = optional(string, "default")
log = optional(bool, false)
no_stats = optional(bool, false)
})), [])
}))
| `[]` | no | +| [subjects](#input\_subjects) | List of contract subjects. Choices `action`: `permit`, `deny`. Default value `action`: `permit`. Choices `priority`: `default`, `level1`, `level2`, `level3`. Default value `priority`: `default`. Default value `log`: `false`. Default value `no_stats`: `false`. Choices `qos_class`: `unspecified`, `level1`, `level2`, `level3`, `level4`, `level5` or`level6`. Default value `qos_class`: `unspecified`. Choices `dscp_target` : `unspecified`, `CS0`, `CS1`, `AF11`, `AF12`, `AF13`, `CS2`, `AF21`, `AF22`, `AF23`, `CS4`, `AF41`, `AF42`, `AF43`, `CS5`, `VA`, `EF`, `CS6` `CS7` or a number between 0 and 63. Default value `dscp_target`: `unspecified` |
list(object({
name = string
alias = optional(string, "")
description = optional(string, "")
reverse_filter_ports = optional(bool, true)
service_graph = optional(string)
qos_class = optional(string, "unspecified")
target_dscp = optional(string, "unspecified")
filters = optional(list(object({
filter = string
action = optional(string, "permit")
priority = optional(string, "default")
log = optional(bool, false)
no_stats = optional(bool, false)
})), [])
consumer_to_provider_service_graph = optional(string)
consumer_to_provider_qos_class = optional(string, "unspecified")
consumer_to_provider_target_dscp = optional(string, "unspecified")
consumer_to_provider_filters = optional(list(object({
filter = string
action = optional(string, "permit")
priority = optional(string, "default")
log = optional(bool, false)
no_stats = optional(bool, false)
})), [])
provider_to_consumer_service_graph = optional(string)
provider_to_consumer_qos_class = optional(string, "unspecified")
provider_to_consumer_target_dscp = optional(string, "unspecified")
provider_to_consumer_filters = optional(list(object({
filter = string
action = optional(string, "permit")
priority = optional(string, "default")
log = optional(bool, false)
no_stats = optional(bool, false)
})), [])
}))
| `[]` | no | ## Outputs @@ -76,6 +76,12 @@ module "aci_contract" { | Name | Type | |------|------| | [aci_rest_managed.vzBrCP](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.vzInTerm](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.vzOutTerm](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.vzRsFiltAtt_ctp](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.vzRsFiltAtt_ptc](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.vzRsInTermGraphAtt](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | +| [aci_rest_managed.vzRsOutTermGraphAtt](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | | [aci_rest_managed.vzRsSubjFiltAtt](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | | [aci_rest_managed.vzRsSubjGraphAtt](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | | [aci_rest_managed.vzSubj](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/rest_managed) | resource | diff --git a/modules/terraform-aci-contract/main.tf b/modules/terraform-aci-contract/main.tf index 66412569..329bddec 100644 --- a/modules/terraform-aci-contract/main.tf +++ b/modules/terraform-aci-contract/main.tf @@ -1,4 +1,3 @@ - locals { subj_filter_list = flatten([ for subj in var.subjects : [ @@ -14,6 +13,36 @@ locals { ]) } +locals { + subj_filter_list_ctp = flatten([ + for subj in var.subjects : [ + for flt in coalesce(subj.consumer_to_provider_filters, []) : { + id = "${subj.name}-${flt.filter}" + subj = subj.name + filter = flt.filter + action = flt.action + directives = join(",", concat(flt.log == true ? ["log"] : [], flt.no_stats == true ? ["no_stats"] : [])) + priority = flt.priority + } + ] + ]) +} + +locals { + subj_filter_list_ptc = flatten([ + for subj in var.subjects : [ + for flt in coalesce(subj.provider_to_consumer_filters, []) : { + id = "${subj.name}-${flt.filter}" + subj = subj.name + filter = flt.filter + action = flt.action + directives = join(",", concat(flt.log == true ? ["log"] : [], flt.no_stats == true ? ["no_stats"] : [])) + priority = flt.priority + } + ] + ]) +} + resource "aci_rest_managed" "vzBrCP" { dn = "uni/tn-${var.tenant}/brc-${var.name}" class_name = "vzBrCP" @@ -35,7 +64,7 @@ resource "aci_rest_managed" "vzSubj" { name = each.value.name nameAlias = each.value.alias descr = each.value.description - revFltPorts = "yes" + revFltPorts = length(each.value.filters) != 0 ? (each.value.reverse_filter_ports ? "yes" : "no") : "no" prio = each.value.qos_class targetDscp = each.value.target_dscp } @@ -54,10 +83,72 @@ resource "aci_rest_managed" "vzRsSubjFiltAtt" { } resource "aci_rest_managed" "vzRsSubjGraphAtt" { - for_each = { for subj in var.subjects : subj.name => subj if subj.service_graph != null } + for_each = { for subj in var.subjects : subj.name => subj if subj.service_graph != null && length(subj.filters) != 0 } dn = "${aci_rest_managed.vzSubj[each.key].dn}/rsSubjGraphAtt" class_name = "vzRsSubjGraphAtt" content = { tnVnsAbsGraphName = each.value.service_graph } } + +resource "aci_rest_managed" "vzInTerm" { + for_each = { for subj in var.subjects : subj.name => subj if length(subj.filters) == 0 } + dn = "${aci_rest_managed.vzSubj[each.key].dn}/intmnl" + class_name = "vzInTerm" + content = { + prio = each.value.consumer_to_provider_qos_class + targetDscp = each.value.consumer_to_provider_target_dscp + } +} + +resource "aci_rest_managed" "vzOutTerm" { + for_each = { for subj in var.subjects : subj.name => subj if length(subj.filters) == 0 } + dn = "${aci_rest_managed.vzSubj[each.key].dn}/outtmnl" + class_name = "vzOutTerm" + content = { + prio = each.value.provider_to_consumer_qos_class + targetDscp = each.value.provider_to_consumer_target_dscp + } +} + +resource "aci_rest_managed" "vzRsFiltAtt_ctp" { + for_each = { for filter in local.subj_filter_list_ctp : filter.id => filter } + dn = "${aci_rest_managed.vzInTerm[each.value.subj].dn}/rsfiltAtt-${each.value.filter}" + class_name = "vzRsFiltAtt" + content = { + action = each.value.action + tnVzFilterName = each.value.filter + directives = each.value.directives + priorityOverride = each.value.priority + } +} + +resource "aci_rest_managed" "vzRsFiltAtt_ptc" { + for_each = { for filter in local.subj_filter_list_ptc : filter.id => filter } + dn = "${aci_rest_managed.vzOutTerm[each.value.subj].dn}/rsfiltAtt-${each.value.filter}" + class_name = "vzRsFiltAtt" + content = { + action = each.value.action + tnVzFilterName = each.value.filter + directives = each.value.directives + priorityOverride = each.value.priority + } +} + +resource "aci_rest_managed" "vzRsInTermGraphAtt" { + for_each = { for subj in var.subjects : subj.name => subj if subj.consumer_to_provider_service_graph != null && length(subj.filters) == 0 } + dn = "${aci_rest_managed.vzInTerm[each.key].dn}/rsInTermGraphAtt" + class_name = "vzRsInTermGraphAtt" + content = { + tnVnsAbsGraphName = each.value.consumer_to_provider_service_graph + } +} + +resource "aci_rest_managed" "vzRsOutTermGraphAtt" { + for_each = { for subj in var.subjects : subj.name => subj if subj.provider_to_consumer_service_graph != null && length(subj.filters) == 0 } + dn = "${aci_rest_managed.vzOutTerm[each.key].dn}/rsOutTermGraphAtt" + class_name = "vzRsOutTermGraphAtt" + content = { + tnVnsAbsGraphName = each.value.provider_to_consumer_service_graph + } +} diff --git a/modules/terraform-aci-contract/variables.tf b/modules/terraform-aci-contract/variables.tf index 4e653eaf..1aba4556 100644 --- a/modules/terraform-aci-contract/variables.tf +++ b/modules/terraform-aci-contract/variables.tf @@ -73,16 +73,16 @@ variable "target_dscp" { } } - variable "subjects" { description = "List of contract subjects. Choices `action`: `permit`, `deny`. Default value `action`: `permit`. Choices `priority`: `default`, `level1`, `level2`, `level3`. Default value `priority`: `default`. Default value `log`: `false`. Default value `no_stats`: `false`. Choices `qos_class`: `unspecified`, `level1`, `level2`, `level3`, `level4`, `level5` or`level6`. Default value `qos_class`: `unspecified`. Choices `dscp_target` : `unspecified`, `CS0`, `CS1`, `AF11`, `AF12`, `AF13`, `CS2`, `AF21`, `AF22`, `AF23`, `CS4`, `AF41`, `AF42`, `AF43`, `CS5`, `VA`, `EF`, `CS6` `CS7` or a number between 0 and 63. Default value `dscp_target`: `unspecified`" type = list(object({ - name = string - alias = optional(string, "") - description = optional(string, "") - service_graph = optional(string) - qos_class = optional(string, "unspecified") - target_dscp = optional(string, "unspecified") + name = string + alias = optional(string, "") + description = optional(string, "") + reverse_filter_ports = optional(bool, true) + service_graph = optional(string) + qos_class = optional(string, "unspecified") + target_dscp = optional(string, "unspecified") filters = optional(list(object({ filter = string action = optional(string, "permit") @@ -90,6 +90,26 @@ variable "subjects" { log = optional(bool, false) no_stats = optional(bool, false) })), []) + consumer_to_provider_service_graph = optional(string) + consumer_to_provider_qos_class = optional(string, "unspecified") + consumer_to_provider_target_dscp = optional(string, "unspecified") + consumer_to_provider_filters = optional(list(object({ + filter = string + action = optional(string, "permit") + priority = optional(string, "default") + log = optional(bool, false) + no_stats = optional(bool, false) + })), []) + provider_to_consumer_service_graph = optional(string) + provider_to_consumer_qos_class = optional(string, "unspecified") + provider_to_consumer_target_dscp = optional(string, "unspecified") + provider_to_consumer_filters = optional(list(object({ + filter = string + action = optional(string, "permit") + priority = optional(string, "default") + log = optional(bool, false) + no_stats = optional(bool, false) + })), []) })) default = [] @@ -118,7 +138,21 @@ variable "subjects" { condition = alltrue([ for s in var.subjects : try(contains(["unspecified", "level1", "level2", "level3", "level4", "level5", "level6"], s.qos_class), false) ]) - error_message = "`qos_class`: Allowed values are `unspecified`, `level1`, `level2`, `level3`, `level4`, `level5` or`level6`" + error_message = "`qos_class`: Allowed values are `unspecified`, `level1`, `level2`, `level3`, `level4`, `level5` or `level6`" + } + + validation { + condition = alltrue([ + for s in var.subjects : try(contains(["unspecified", "level1", "level2", "level3", "level4", "level5", "level6"], s.consumer_to_provider_qos_class), false) + ]) + error_message = "`consumer_to_provider_qos_class`: Allowed values are `unspecified`, `level1`, `level2`, `level3`, `level4`, `level5` or `level6`" + } + + validation { + condition = alltrue([ + for s in var.subjects : try(contains(["unspecified", "level1", "level2", "level3", "level4", "level5", "level6"], s.provider_to_consumer_qos_class), false) + ]) + error_message = "`provider_to_consumer_qos_class`: Allowed values are `unspecified`, `level1`, `level2`, `level3`, `level4`, `level5` or `level6`" } validation { @@ -128,6 +162,20 @@ variable "subjects" { error_message = "`target_dscp`: Allowed values are `unspecified`, `CS0`, `CS1`, `AF11`, `AF12`, `AF13`, `CS2`, `AF21`, `AF22`, `AF23`, `CS4`, `AF41`, `AF42`, `AF43`, `CS5`, `VA`, `EF`, `CS6`, `CS7` or a number between 0 and 63." } + validation { + condition = alltrue([ + for s in var.subjects : try(contains(["unspecified", "CS0", "CS1", "AF11", "AF12", "AF13", "CS2", "AF21", "AF22", "AF23", "CS4", "AF41", "AF42", "AF43", "CS5", "VA", "EF", "CS6", "CS7"], s.consumer_to_provider_target_dscp), false) || try(tonumber(s.consumer_to_provider_target_dscp) >= 0 && tonumber(s.consumer_to_provider_target_dscp) <= 63, false) + ]) + error_message = "`consumer_to_provider_target_dscp`: Allowed values are `unspecified`, `CS0`, `CS1`, `AF11`, `AF12`, `AF13`, `CS2`, `AF21`, `AF22`, `AF23`, `CS4`, `AF41`, `AF42`, `AF43`, `CS5`, `VA`, `EF`, `CS6`, `CS7` or a number between 0 and 63." + } + + validation { + condition = alltrue([ + for s in var.subjects : try(contains(["unspecified", "CS0", "CS1", "AF11", "AF12", "AF13", "CS2", "AF21", "AF22", "AF23", "CS4", "AF41", "AF42", "AF43", "CS5", "VA", "EF", "CS6", "CS7"], s.provider_to_consumer_target_dscp), false) || try(tonumber(s.provider_to_consumer_target_dscp) >= 0 && tonumber(s.provider_to_consumer_target_dscp) <= 63, false) + ]) + error_message = "`provider_to_consumer_target_dscp`: Allowed values are `unspecified`, `CS0`, `CS1`, `AF11`, `AF12`, `AF13`, `CS2`, `AF21`, `AF22`, `AF23`, `CS4`, `AF41`, `AF42`, `AF43`, `CS5`, `VA`, `EF`, `CS6`, `CS7` or a number between 0 and 63." + } + validation { condition = alltrue([ for s in var.subjects : s.service_graph == null || can(regex("^[a-zA-Z0-9_.:-]{0,64}$", s.service_graph)) @@ -135,6 +183,20 @@ variable "subjects" { error_message = "`service_graph`: Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." } + validation { + condition = alltrue([ + for s in var.subjects : s.consumer_to_provider_service_graph == null || can(regex("^[a-zA-Z0-9_.:-]{0,64}$", s.consumer_to_provider_service_graph)) + ]) + error_message = "`consumer_to_provider_service_graph`: Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." + } + + validation { + condition = alltrue([ + for s in var.subjects : s.provider_to_consumer_service_graph == null || can(regex("^[a-zA-Z0-9_.:-]{0,64}$", s.provider_to_consumer_service_graph)) + ]) + error_message = "`provider_to_consumer_service_graph`: Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." + } + validation { condition = alltrue(flatten([ for s in var.subjects : [for f in coalesce(s.filters, []) : can(regex("^[a-zA-Z0-9_.:-]{0,64}$", f.filter))] @@ -142,6 +204,20 @@ variable "subjects" { error_message = "`filter`: Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." } + validation { + condition = alltrue(flatten([ + for s in var.subjects : [for f in coalesce(s.consumer_to_provider_filters, []) : can(regex("^[a-zA-Z0-9_.:-]{0,64}$", f.filter))] + ])) + error_message = "`filter`: Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." + } + + validation { + condition = alltrue(flatten([ + for s in var.subjects : [for f in coalesce(s.provider_to_consumer_filters, []) : can(regex("^[a-zA-Z0-9_.:-]{0,64}$", f.filter))] + ])) + error_message = "`filter`: Allowed characters: `a`-`z`, `A`-`Z`, `0`-`9`, `_`, `.`, `:`, `-`. Maximum characters: 64." + } + validation { condition = alltrue(flatten([ for s in var.subjects : [for f in coalesce(s.filters, []) : f.action == null || try(contains(["permit", "deny"], f.action), false)] @@ -149,10 +225,47 @@ variable "subjects" { error_message = "`action`: Allowed values are `permit` or `deny`." } + validation { + condition = alltrue(flatten([ + for s in var.subjects : [for f in coalesce(s.provider_to_consumer_filters, []) : f.action == null || try(contains(["permit", "deny"], f.action), false)] + ])) + error_message = "`action`: Allowed values are `permit` or `deny`." + } + + validation { + condition = alltrue(flatten([ + for s in var.subjects : [for f in coalesce(s.consumer_to_provider_filters, []) : f.action == null || try(contains(["permit", "deny"], f.action), false)] + ])) + error_message = "`action`: Allowed values are `permit` or `deny`." + } + validation { condition = alltrue(flatten([ for s in var.subjects : [for f in coalesce(s.filters, []) : f.priority == null || try(contains(["default", "level1", "level2", "level3"], f.priority), false)] ])) error_message = "`priority`: Allowed values are `default`, `level1`, `level2` or `level3`." } -} + + validation { + condition = alltrue(flatten([ + for s in var.subjects : [for f in coalesce(s.provider_to_consumer_filters, []) : f.priority == null || try(contains(["default", "level1", "level2", "level3"], f.priority), false)] + ])) + error_message = "`priority`: Allowed values are `default`, `level1`, `level2` or `level3`." + } + + validation { + condition = alltrue(flatten([ + for s in var.subjects : [for f in coalesce(s.consumer_to_provider_filters, []) : f.priority == null || try(contains(["default", "level1", "level2", "level3"], f.priority), false)] + ])) + error_message = "`priority`: Allowed values are `default`, `level1`, `level2` or `level3`." + } + + validation { + condition = alltrue([ + for s in var.subjects : + (length(s.filters) == 0 || + (length(s.filters) != 0 && (length(s.provider_to_consumer_filters) == 0 && length(s.consumer_to_provider_filters) == 0))) + ]) + error_message = "`filters`: When `provider_to_consumer_filters` and/or `consumer_to_provider_filters` are specified, bidirectional `filters` are not allowed in the same subject.`" + } +} \ No newline at end of file