diff --git a/plugins/module_utils/network/vyos/config/nat/nat.py b/plugins/module_utils/network/vyos/config/nat/nat.py index 97536b38..0836259b 100644 --- a/plugins/module_utils/network/vyos/config/nat/nat.py +++ b/plugins/module_utils/network/vyos/config/nat/nat.py @@ -1,140 +1,151 @@ # # -*- coding: utf-8 -*- # Copyright 2021 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # from __future__ import absolute_import, division, print_function __metaclass__ = type """ The vyos_nat config file. It is in this file where the current configuration (as dict) is compared to the provided configuration (as dict) and the command set necessary to bring the current configuration to its desired end-state is created. """ from copy import deepcopy from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( ResourceModule, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.nat import ( NatTemplate, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import combine # from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( # dict_merge, # ) class Nat(ResourceModule): """ The vyos_nat config class """ def __init__(self, module): super(Nat, self).__init__( empty_fact_val={}, facts_module=Facts(module), module=module, resource="nat", tmplt=NatTemplate(), ) self.parsers = [ "nat.cgnat.log_allocation", - "nat.cgnat.pool.external.range", - "nat.cgnat.pool.external.external_port_range", - "nat.cgnat.pool.external.external_per_user", - "cgnat_pool_internal_range", - "cgnat_rule_source_pool", - "cgnat_rule_translation_pool", + "nat.cgnat.pool.external", + # "nat.cgnat.pool.external.range", + # "nat.cgnat.pool.external.external_port_range", + # "nat.cgnat.pool.external.external_per_user", + "nat.cgnat.pool.internal.range", + "nat.cgnat.rule.source.pool", + "nat.cgnat.rule.translation.pool", "nat_type_description", "nat_type_protocol", "nat_type_disable", "nat_type_exclude", "nat_type_log", "nat_type_address", "nat_type_prefix", "nat_type_fqdn", "nat_type_port", "nat_type_translation_address", "nat_type_translation_port", "nat_inbound_interface_name", "nat_inbound_interface_group", "nat_static_inbound_interface", "nat6x_inbound_interface", "nat_type_outbound_interface", "nat_type_outbound_interface_group", "nat_type_address_group", "nat_type_packet_type", "nat_type_lb_backend", "nat_type_lb_hash", "nat_type_translation_options", "nat_type_translation_redirect", "nat64_match_mark", "nat64_translation_pool_address", "nat64_translation_pool_description", "nat64_translation_pool_disable", "nat64_translation_pool_port", "nat64_translation_pool_protocol", ] def execute_module(self): """Execute the module :rtype: A dictionary :returns: The result from module execution """ if self.state not in ["parsed", "gathered"]: self.generate_commands() self.run_commands() return self.result def generate_commands(self): """Generate configuration commands to send based on want, have and desired state. """ wantd = {} haved = {} wantd = deepcopy(self.want) haved = deepcopy(self.have) # if state is merged, merge want onto have and then compare if self.state == "merged": # wantd = dict_merge(haved, wantd) wantd = combine(haved, wantd, recursive=True, list_merge="append_rp") - + self._normalize_pool_list_to_dict(wantd) # self._module.fail_json(msg={"want": wantd, " ******** have": haved},) # self._module.fail_json(msg={"merged": wantd, " ******** have": haved, "******** original want": self.want},) - wantd = { - "nat": { - "cgnat": { - "pool": { - "external": { - "ext-pool-1": { - "name": "ext-pool-1", - "range": [ - { - "address": "192.168.1.0/24", - "seq": 1, - }, - ], - }, - }, - }, - }, - }, - } + # wantd = { + # "nat": { + # "cgnat": { + # "pool": { + # "external": { + # "ext-pool-1": { + # "name": "ext-pool-1", + # "range": [ + # { + # "address": "192.168.1.0/24", + # "seq": 1, + # }, + # ], + # }, + # }, + # }, + # }, + # }, + # } # wantd = {"nat": {"cgnat": {"pool": {"external": {}}}}} - haved = {} + # haved = {} self.compare(parsers=self.parsers, want=wantd, have=haved) self._module.fail_json(msg={"commands": self.commands}) + + def _normalize_pool_list_to_dict(self, config): + """Convert pool.external and pool.internal from list to name-keyed dict.""" + cgnat = config.get("nat", {}).get("cgnat", {}) + pool = cgnat.get("pool", {}) + + for pool_type in ("external", "internal"): + entries = pool.get(pool_type) + if isinstance(entries, list): + pool[pool_type] = {item["name"]: item for item in entries} diff --git a/plugins/module_utils/network/vyos/facts/nat/nat.py b/plugins/module_utils/network/vyos/facts/nat/nat.py index 1fe7219a..c19414db 100644 --- a/plugins/module_utils/network/vyos/facts/nat/nat.py +++ b/plugins/module_utils/network/vyos/facts/nat/nat.py @@ -1,175 +1,176 @@ # -*- coding: utf-8 -*- # Copyright 2021 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type """ The vyos ntp fact class It is in this file the configuration is collected from the device for a given resource, parsed, and the facts tree is populated based on the configuration. """ import re from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.nat.nat import ( NatArgs, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.nat import ( NatTemplate, ) class NatFacts(object): """The vyos nat facts class""" def __init__(self, module, subspec="config", options="options"): self._module = module self.argument_spec = NatArgs.argument_spec def get_config(self, connection): - return connection.get("show configuration commands | match 'nat'") + return connection.get("show configuration commands | grep nat") def populate_facts(self, connection, ansible_facts, data=None): """Populate the facts for NAT network resource :param connection: the device connection :param ansible_facts: Facts dictionary :param data: previously collected conf :rtype: dictionary :returns: facts """ facts = {} objs = [] config_lines = [] if not data: data = self.get_config(connection) for resource in data.splitlines(): config_lines.append(re.sub(r"'([^']*)'", r"\1", resource)) nat_parser = NatTemplate(lines=config_lines, module=self._module) objs = nat_parser.parse() - self._module.fail_json(msg=objs) - objs = self._normalise(objs) + # self._module.fail_json(msg=objs) + # objs = self._normalise(objs) ansible_facts["ansible_network_resources"].pop("nat", None) params = utils.remove_empties( nat_parser.validate_config(self.argument_spec, {"config": objs}, redact=True), ) if params.get("config"): facts["nat"] = params["config"] ansible_facts["ansible_network_resources"].update(facts) + # self._module.fail_json(msg=ansible_facts) return ansible_facts def _deep_merge(self, base, override): for k, v in override.items(): if k in base and isinstance(base[k], dict) and isinstance(v, dict): self._deep_merge(base[k], v) elif k in base and isinstance(base[k], list) and isinstance(v, list): for entry in v: if entry not in base[k]: base[k].append(entry) else: base[k] = v return base def _merge_rule_list(self, rules): merged = {} for item in rules: rid = item["id"] if rid not in merged: merged[rid] = {"id": rid} for k, v in item.items(): if k == "id": continue if isinstance(v, list): existing = merged[rid].setdefault(k, []) for entry in v: if entry not in existing: existing.append(entry) elif isinstance(v, dict): merged[rid].setdefault(k, {}) self._deep_merge(merged[rid][k], v) else: merged[rid][k] = v return list(merged.values()) def _merge_pool_list(self, pools): merged = {} for item in pools: name = item["name"] if name not in merged: merged[name] = {"name": name} for k, v in item.items(): if k == "name": continue if k == "range" and isinstance(v, list): existing = merged[name].setdefault(k, []) existing.extend(v) merged[name][k] = self._merge_range_list(existing) elif isinstance(v, list): merged[name].setdefault(k, []) for val in v: if val not in merged[name][k]: merged[name][k].append(val) elif isinstance(v, dict): merged[name].setdefault(k, {}) self._deep_merge(merged[name][k], v) else: merged[name][k] = v return list(merged.values()) def _normalise(self, objs): for nat_type in ["nat", "nat64", "nat66"]: nat = objs.get(nat_type) if not nat: continue for section in ["destination", "source", "static", "cgnat"]: if section in nat and "rule" in nat[section]: nat[section]["rule"] = self._merge_rule_list(nat[section]["rule"]) nat[section]["rule"].sort(key=lambda x: x.get("id", 0)) if "cgnat" in nat and "pool" in nat["cgnat"]: pool = nat["cgnat"]["pool"] for ptype in ["external", "internal"]: if ptype in pool: pool[ptype] = self._merge_pool_list(pool[ptype]) if nat_type == "nat64": for rule in nat.get("source", {}).get("rule", []): pools = rule.get("translation", {}).get("pool") if pools: rule["translation"]["pool"] = self._merge_rule_list(pools) rule["translation"]["pool"].sort(key=lambda x: x.get("id", 0)) return objs def _merge_range_list(self, ranges): """Merge range entries by value, preserving seq.""" merged = {} for entry in ranges: if isinstance(entry, dict): key = entry["address"] if key not in merged: merged[key] = {"address": key} if entry.get("seq"): merged[key]["seq"] = entry["seq"] else: # fallback for plain strings during transition merged[entry] = entry return list(merged.values()) diff --git a/plugins/module_utils/network/vyos/rm_templates/nat.py b/plugins/module_utils/network/vyos/rm_templates/nat.py index 99bab24a..8befccad 100644 --- a/plugins/module_utils/network/vyos/rm_templates/nat.py +++ b/plugins/module_utils/network/vyos/rm_templates/nat.py @@ -1,1142 +1,1204 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, division, print_function __metaclass__ = type import re from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( NetworkTemplate, ) +def _tmplt_cgnat_pool_ext(config_data): + config_data = config_data["nat"]["cgnat"]["pool"]["external"] + command = [] + # cmd = "nat cgnat pool external {name}".format(**config_data) + cmd = "nat cgnat pool external {name}".format(**config_data) + + # for key, value in config_data.items(): + # if ( + # key == "name" + # or isinstance(value, dict) + # or isinstance(value, list) + # or isinstance(value, bool) + # or value is None + # ): + # continue + # else: + # if key == "description": + # value = f"'{value}'" + # command.append(f"{cmd} {key.replace('_', '-')} {value}") + return str(cmd) + + class NatTemplate(NetworkTemplate): def __init__(self, lines=None, module=None): prefix = {"set": "set", "remove": "delete"} super(NatTemplate, self).__init__(lines=lines, tmplt=self, prefix=prefix, module=module) # fmt: off PARSERS = [ # # ------------------------- # CGNAT (keep explicit) # ------------------------- # { "name": "nat.cgnat.log_allocation", "getval": re.compile( r""" ^set \s+nat \s+cgnat \s+log-allocation $""", re.VERBOSE, ), "setval": "nat cgnat log-allocation", "result": { "nat": { "cgnat": { "log_allocation": True, }, }, }, }, { - "name": "nat.cgnat.pool.external.range", + "name": "nat.cgnat.pool.external", "getval": re.compile( r""" - ^set - \s+nat - \s+cgnat - \s+pool - \s+external - \s+(?P\S+) - \s+range - \s+(?P\S+)(?:\s+seq\s+(?P\d+))? - $""", + ^set\s+nat\s+cgnat\s+pool\s+external\s+(?P\S+) + (?: + \s+external-port-range\s+(?P\S+) + | \s+range\s+(?P\S+)(?:\s+seq\s+(?P\d+))? + | \s+per-user-limit\s+port\s+(?P\d+) + )? + $ + """, re.VERBOSE, ), - "setval": "nat cgnat pool external {{ name }} range {{ range }}{% if seq is defined %} seq {{ seq }}{% endif %}", + "setval": _tmplt_cgnat_pool_ext, "result": { "nat": { "cgnat": { "pool": { "external": { "{{ name }}": { "name": "{{ name }}", + "external_port_range": "{{ port_range }}", + "per_user_limit": { + "port": "{{ limit }}", + }, "range": [ { "address": "{{ range }}", "seq": "{{ seq }}", }, ], }, }, }, }, }, }, }, - { - "name": "nat.cgnat.pool.external.external_port_range", - "getval": re.compile( - r""" - ^set - \s+nat - \s+cgnat - \s+pool - \s+external - \s+(?P\S+) - \s+external-port-range - \s+(?P\S+) - $""", - re.VERBOSE, - ), - "setval": "nat cgnat pool external {{ name }} external-port-range {{ range }}", - "result": { - "nat": { - "cgnat": { - "pool": { - "external": { - "{{ name }}": { - "name": "{{ name }}", - "external_port_range": "{{ range }}", - }, - }, - }, - }, - }, - }, - }, - { - "name": "nat.cgnat.pool.external.external_per_user", - "getval": re.compile( - r""" - ^set - \s+nat - \s+cgnat - \s+pool - \s+external - \s+(?P\S+) - \s+per-user-limit - \s+port - \s+(?P\d+) - $""", - re.VERBOSE, - ), - "setval": "nat cgnat pool external {{ name }} per-user-limit port {{ limit }}", - "result": { - "nat": { - "cgnat": { - "pool": { - "external": { - "{{ name }}": { - "name": "{{ name }}", - "per_user_limit": {"port": "{{ limit }}"}, - }, - }, - }, - }, - }, - }, - }, + # { + # "name": "nat.cgnat.pool.external.range", + # "getval": re.compile( + # r""" + # ^set + # \s+nat + # \s+cgnat + # \s+pool + # \s+external + # \s+(?P\S+) + # \s+range + # \s+(?P\S+)(?:\s+seq\s+(?P\d+))? + # $""", + # re.VERBOSE, + # ), + # "setval": "nat cgnat pool external {{ name }} range {{ range }}{% if seq is defined %} seq {{ seq }}{% endif %}", + # "result": { + # "nat": { + # "cgnat": { + # "pool": { + # "external": { + # "{{ name }}": { + # "name": "{{ name }}", + # "range": [ + # { + # "address": "{{ range }}", + # "seq": "{{ seq }}", + # }, + # ], + # }, + # }, + # }, + # }, + # }, + # }, + # }, + # { + # "name": "nat.cgnat.pool.external.external_port_range", + # "getval": re.compile( + # r""" + # ^set + # \s+nat + # \s+cgnat + # \s+pool + # \s+external + # \s+(?P\S+) + # \s+external-port-range + # \s+(?P\S+) + # $""", + # re.VERBOSE, + # ), + # # "setval": "nat cgnat pool external {{ name }} external-port-range {{ port_range }}", + # "setval": "test", + # "result": { + # "nat": { + # "cgnat": { + # "pool": { + # "external": { + # "{{ name }}": { + # "name": "{{ name }}", + # "external_port_range": "{{ port_range }}", + # }, + # }, + # }, + # }, + # }, + # }, + # }, + # { + # "name": "nat.cgnat.pool.external.external_per_user", + # "getval": re.compile( + # r""" + # ^set + # \s+nat + # \s+cgnat + # \s+pool + # \s+external + # \s+(?P\S+) + # \s+per-user-limit + # \s+port + # \s+(?P\d+) + # $""", + # re.VERBOSE, + # ), + # "setval": "nat cgnat pool external {{ name }} per-user-limit port {{ limit }}", + # "result": { + # "nat": { + # "cgnat": { + # "pool": { + # "external": { + # "{{ name }}": { + # "name": "{{ name }}", + # "per_user_limit": {"port": "{{ limit }}"}, + # }, + # }, + # }, + # }, + # }, + # }, + # }, { "name": "nat.cgnat.pool.internal.range", "getval": re.compile( r""" ^set \s+nat \s+cgnat \s+pool \s+internal \s+(?P\S+) \s+range \s+(?P\S+) $""", re.VERBOSE, ), "setval": "nat cgnat pool internal {{ name }} range {{ range }}", "result": { "nat": { "cgnat": { "pool": { "internal": { "{{ name }}": { "name": "{{ name }}", "range": ["{{ range }}"], }, }, }, }, }, }, }, { - "name": "cgnat_rule_source_pool", + "name": "nat.cgnat.rule.source.pool", "getval": re.compile( r""" ^set \s+nat \s+cgnat \s+rule \s+(?P\d+) \s+source \s+pool \s+(?P\S+) $""", re.VERBOSE, ), "setval": "nat cgnat rule {{ id }} source pool {{ pool }}", "result": { "nat": { "cgnat": { "rule": { "{{ id }}": { "id": "{{ id }}", "source": {"pool": "{{ pool }}"}, }, }, }, }, }, }, { - "name": "cgnat_rule_translation_pool", + "name": "nat.cgnat.rule.translation.pool", "getval": re.compile( r""" ^set \s+nat \s+cgnat \s+rule \s+(?P\d+) \s+translation \s+pool \s+(?P\S+) $""", re.VERBOSE, ), "setval": "nat cgnat rule {{ id }} translation pool {{ pool }}", "result": { "nat": { "cgnat": { "rule": { "{{ id }}": { "id": "{{ id }}", "translation": {"pool": "{{ pool }}"}, }, }, }, }, }, }, # # ------------------------- # GENERIC NAT (destination/source/static) # ------------------------- # # description { "name": "nat_type_description", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source|static) \s+rule \s+(?P\S+) \s+description \s+(?P.+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} description {{ description }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "description": "{{ description }}", }, ], }, }, }, }, # protocol { "name": "nat_type_protocol", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source|static) \s+rule \s+(?P\S+) \s+protocol \s+(?P\S+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} protocol {{ protocol }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "protocol": "{{ protocol }}", }, ], }, }, }, }, # flags { "name": "nat_type_disable", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source|static) \s+rule \s+(?P\S+) \s+disable $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} disable", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "disable": True, }, ], }, }, }, }, { "name": "nat_type_exclude", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source|static) \s+rule \s+(?P\S+) \s+exclude $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} exclude", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "exclude": True, }, ], }, }, }, }, { "name": "nat_type_log", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source|static) \s+rule \s+(?P\S+) \s+log $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} log", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "log": True, }, ], }, }, }, }, # address (destination/source) { "name": "nat_type_address", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source|static) \s+rule \s+(?P\S+) \s+(?Pdestination|source) \s+address \s+(?P\S+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} {{ atype }} address {{ value }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "{{ atype }}": {"address": "{{ value }}"}, }, ], }, }, }, }, # prefix (destination/source) { "name": "nat_type_prefix", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source|static) \s+rule \s+(?P\S+) \s+(?Pdestination|source) \s+prefix \s+(?P\S+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} {{ atype }} prefix {{ value }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "{{ atype }}": {"prefix": "{{ value }}"}, }, ], }, }, }, }, # fqdn { "name": "nat_type_fqdn", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source) \s+rule \s+(?P\S+) \s+(?Pdestination|source) \s+fqdn \s+(?P\S+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} {{ atype }} fqdn {{ value }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "{{ atype }}": {"fqdn": "{{ value }}"}, }, ], }, }, }, }, # port { "name": "nat_type_port", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source) \s+rule \s+(?P\S+) \s+(?Pdestination|source) \s+port \s+(?P\S+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} {{ atype }} port {{ value }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "{{ atype }}": {"port": "{{ value }}"}, }, ], }, }, }, }, # translation address { "name": "nat_type_translation_address", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source|static) \s+rule \s+(?P\S+) \s+translation \s+address \s+(?P\S+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} translation address {{ value }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "translation": {"address": "{{ value }}"}, }, ], }, }, }, }, # translation port { "name": "nat_type_translation_port", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source) \s+rule \s+(?P\S+) \s+translation \s+port \s+(?P\S+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} translation port {{ value }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "translation": {"port": "{{ value }}"}, }, ], }, }, }, }, { "name": "nat_inbound_interface_name", "getval": re.compile( r""" ^set \s+nat \s+(?Pdestination|source) \s+rule \s+(?P\S+) \s+inbound-interface \s+name \s+(?P\S+) $""", re.VERBOSE, ), "setval": "nat {{ type }} rule {{ id }} inbound-interface name {{ value }}", "result": { "nat": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "inbound_interface": {"name": "{{ value }}"}, }, ], }, }, }, }, { "name": "nat_inbound_interface_group", "getval": re.compile( r""" ^set \s+nat \s+(?Pdestination|source) \s+rule \s+(?P\S+) \s+inbound-interface \s+group \s+(?P\S+) $""", re.VERBOSE, ), "setval": "nat {{ type }} rule {{ id }} inbound-interface group {{ value }}", "result": { "nat": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "inbound_interface": {"group": "{{ value }}"}, }, ], }, }, }, }, { "name": "nat_static_inbound_interface", "getval": re.compile( r""" ^set \s+nat \s+static \s+rule \s+(?P\S+) \s+inbound-interface \s+(?P\S+) $""", re.VERBOSE, ), "setval": "nat static rule {{ id }} inbound-interface {{ value }}", "result": { "nat": { "static": { "rule": [ { "id": "{{ id }}", "inbound_interface": "{{ value }}", }, ], }, }, }, }, # NAT6X inbound interface { "name": "nat6x_inbound_interface", "getval": re.compile( r""" ^set \s+(?Pnat64|nat66) \s+(?Pdestination|source|static) \s+rule \s+(?P\S+) \s+inbound-interface \s+name \s+(?P\S+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} inbound-interface name {{ value }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "inbound_interface": {"name": "{{ value }}"}, }, ], }, }, }, }, # outbound interface { "name": "nat_type_outbound_interface", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source|static) \s+rule \s+(?P\S+) \s+outbound-interface \s+name \s+(?P\S+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} outbound-interface name {{ value }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "outbound_interface": {"name": "{{ value }}"}, }, ], }, }, }, }, { "name": "nat_type_outbound_interface_group", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source|static) \s+rule \s+(?P\S+) \s+outbound-interface \s+group \s+(?P\S+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} outbound-interface group {{ value }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "outbound_interface": {"group": "{{ value }}"}, }, ], }, }, }, }, { "name": "nat_type_address_group", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source) \s+rule \s+(?P\S+) \s+(?Pdestination|source) \s+group \s+(?Paddress-group|domain-group|mac-group|network-group|port-group) \s+(?P\S+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} {{ atype }} group {{ gtype }} {{ value }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "{{ atype }}": { "{{ gtype | replace('-', '_') }}": "{{ value }}", }, }, ], }, }, }, }, # packet type { "name": "nat_type_packet_type", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source) \s+rule \s+(?P\S+) \s+packet-type \s+(?P\S+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} packet-type {{ value }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "packet_type": "{{ value }}", }, ], }, }, }, }, # load balance backend { "name": "nat_type_lb_backend", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source) \s+rule \s+(?P\S+) \s+load-balance \s+backend \s+(?P\S+) \s+weight \s+(?P\d+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} load-balance backend {{ ip }} weight {{ weight }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "load_balance": { "backend": { "ip": "{{ ip }}", "weight": "{{ weight }}", }, }, }, ], }, }, }, }, # load balance hash { "name": "nat_type_lb_hash", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source) \s+rule \s+(?P\S+) \s+load-balance \s+hash \s+(?P\S+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} load-balance hash {{ value }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "load_balance": {"hash": "{{ value }}"}, }, ], }, }, }, }, # translation options { "name": "nat_type_translation_options", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source) \s+rule \s+(?P\S+) \s+translation \s+options \s+(?Paddress-mapping|port-mapping) \s+(?P\S+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} translation options {{ opt }} {{ value }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "translation": { "{{ opt | replace(\"-\", \"_\") }}": "{{ value }}", }, }, ], }, }, }, }, # redirect port { "name": "nat_type_translation_redirect", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source) \s+rule \s+(?P\S+) \s+translation \s+redirect \s+port \s+(?P\S+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} translation redirect port {{ value }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "translation": { "redirect_port": "{{ value }}", }, }, ], }, }, }, }, { "name": "nat64_match_mark", "getval": re.compile( r""" ^set \s+nat64 \s+source \s+rule \s+(?P\S+) \s+match \s+mark \s+(?P\d+) $""", re.VERBOSE, ), "setval": "nat64 source rule {{ id }} match mark {{ mark }}", "result": { "nat64": { "source": { "rule": [ { "id": "{{ id }}", "match": {"mark": "{{ mark }}"}, }, ], }, }, }, }, { "name": "nat64_translation_pool_address", "getval": re.compile( r""" ^set \s+nat64 \s+source \s+rule \s+(?P\S+) \s+translation \s+pool \s+(?P\d+) \s+address \s+(?P\S+) $""", re.VERBOSE, ), "setval": "nat64 source rule {{ id }} translation pool {{ pool_id }} address {{ value }}", "result": { "nat64": { "source": { "rule": [ { "id": "{{ id }}", "translation": { "pool": [{"id": "{{ pool_id }}", "address": "{{ value }}"}], }, }, ], }, }, }, }, { "name": "nat64_translation_pool_description", "getval": re.compile( r""" ^set \s+nat64 \s+source \s+rule \s+(?P\S+) \s+translation \s+pool \s+(?P\d+) \s+description \s+(?P.+) $""", re.VERBOSE, ), "setval": "nat64 source rule {{ id }} translation pool {{ pool_id }} description {{ value }}", "result": { "nat64": { "source": { "rule": [ { "id": "{{ id }}", "translation": { "pool": [{"id": "{{ pool_id }}", "description": "{{ value }}"}], }, }, ], }, }, }, }, { "name": "nat64_translation_pool_disable", "getval": re.compile( r""" ^set \s+nat64 \s+source \s+rule \s+(?P\S+) \s+translation \s+pool \s+(?P\d+) \s+disable $""", re.VERBOSE, ), "setval": "nat64 source rule {{ id }} translation pool {{ pool_id }} disable", "result": { "nat64": { "source": { "rule": [ { "id": "{{ id }}", "translation": { "pool": [{"id": "{{ pool_id }}", "disable": True}], }, }, ], }, }, }, }, { "name": "nat64_translation_pool_port", "getval": re.compile( r""" ^set \s+nat64 \s+source \s+rule \s+(?P\S+) \s+translation \s+pool \s+(?P\d+) \s+port \s+(?P\S+) $""", re.VERBOSE, ), "setval": "nat64 source rule {{ id }} translation pool {{ pool_id }} port {{ value }}", "result": { "nat64": { "source": { "rule": [ { "id": "{{ id }}", "translation": { "pool": [{"id": "{{ pool_id }}", "port": "{{ value }}"}], }, }, ], }, }, }, }, { "name": "nat64_translation_pool_protocol", "getval": re.compile( r""" ^set \s+nat64 \s+source \s+rule \s+(?P\S+) \s+translation \s+pool \s+(?P\d+) \s+protocol \s+(?P\S+) $""", re.VERBOSE, ), "setval": "nat64 source rule {{ id }} translation pool {{ pool_id }} protocol {{ value }}", "result": { "nat64": { "source": { "rule": [ { "id": "{{ id }}", "translation": { "pool": [{"id": "{{ pool_id }}", "protocol": "{{ value }}"}], }, }, ], }, }, }, }, ] # fmt: on