diff --git a/AGENTS.md b/AGENTS.md index 436b6785..6a749230 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,39 +1,46 @@ # AGENTS.md ## Project purpose + The official Ansible Collection for managing VyOS network appliances (`vyos.vyos` namespace). Provides modules, plugins, action handlers, terminal plugins, and resource modules for BGP, OSPF, firewall, interfaces, NTP, etc. ## Tech stack + - Ansible Collection (Galaxy). Python control-plane code under `plugins/`. - `galaxy.yml` declares `namespace: vyos`, `name: vyos`, `version: 6.0.0`, dep `ansible.netcommon >= 2.5.1`, license_file `LICENSE` (GPL-3.0). - Test stack: `pytest` + `tox-ansible.ini`; lint via flake8, isort, black (line-length 100), pre-commit, ansible-lint. - Runtime deps: `paramiko`, `scp` (`requirements.txt`); `bindep.txt` for system deps. ## Build / test / run + - Build: `ansible-galaxy collection build` produces a `vyos-vyos-.tar.gz`. - Install local dev: `ansible-galaxy collection install . --force`. - Test (unit): `ansible-test units` (matches `unit-galaxy` CI job; requires collection installed under `~/.ansible/collections/`). Fast local alternative: `source .venv/bin/activate && PYTHONPATH=".collections" python -m pytest tests/unit` (matches `unit-source` CI path; config in `pyproject.toml`). CI (`.github/workflows/tests.yml`) runs the changelog / build-import / ansible-lint / sanity / unit-galaxy / unit-source jobs; integration tests live under `tests/integration/` but are not yet wired into CI. Per `README.md`, the collection targets VyOS 1.3.8 / 1.4.1 / 1.5-rolling (no version matrix in the workflow itself). ## Repository layout + - `plugins/{action,cliconf,doc_fragments,filter,inventory,module_utils,modules,terminal}/` — collection content. - `tests/` — sanity, unit, integration directories. CI (`.github/workflows/tests.yml`) runs sanity + unit-galaxy + unit-source (plus changelog / build-import / ansible-lint); integration is not yet wired into CI. - `docs/` — generated module docs. - `meta/`, `changelogs/`, `CHANGELOG.rst` — Galaxy + release metadata. - `pyproject.toml` (black/pytest config), `.flake8`, `.isort.cfg`, `.ansible-lint`, `.pre-commit-config.yaml`. - `.github/workflows/` — `tests.yml`, `release.yml`, `codecoverage.yml`, `cla-check.yml`, `ah_token_refresh.yml`, `check_label.yaml`. ## Cross-repo context + - Consumed by Ansible users running playbooks against VyOS routers built by `vyos/vyos-build`. - The `vyos.vyos` collection talks to VyOS via `network_cli` connections; supports the same train branches (`current`, `circinus`, `sagitta`, `equuleus`). ## Conventions + - Commit headline: `T12345: description` (Phorge ID at https://vyos.dev mandatory). No workflow enforces PR title format in this repo. - Every PR must include exactly one changelog fragment under `changelogs/fragments/`; use `doc_changes` for documentation-only updates, or `trivial` for tooling / housekeeping changes. - Default branch `main` (not `current` — this repo predates the rename convention). - Issues tracked at https://vyos.dev (see `galaxy.yml`). - Codecov + CodeRabbit configured (`codecov.yml`, `.coderabbit.yaml`). ## Notes for future contributors + - Galaxy versioning is independent of VyOS train versioning — bump in `galaxy.yml` per release. - Tested matrix is in README; expand only after smoketesting against real images. - `PR408_README.md` plus `pr408-diagram.png` document a non-trivial historical refactor; read before touching resource-module structure. diff --git a/plugins/module_utils/network/vyos/config/nat/nat.py b/plugins/module_utils/network/vyos/config/nat/nat.py index 3bf7704b..97536b38 100644 --- a/plugins/module_utils/network/vyos/config/nat/nat.py +++ b/plugins/module_utils/network/vyos/config/nat/nat.py @@ -1,121 +1,140 @@ # # -*- 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 = [ - "cgnat_log_allocation", - "cgnat_pool_external_range", - "cgnat_pool_external_port_range", - "cgnat_pool_external_per_user", + "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_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._module.fail_json( - # msg={"merged": wantd, " ******** have": haved, "******** original want": self.want}, - # ) - + # 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": {}}}}} + haved = {} self.compare(parsers=self.parsers, want=wantd, have=haved) self._module.fail_json(msg={"commands": self.commands}) diff --git a/plugins/module_utils/network/vyos/facts/nat/nat.py b/plugins/module_utils/network/vyos/facts/nat/nat.py index a022252a..1fe7219a 100644 --- a/plugins/module_utils/network/vyos/facts/nat/nat.py +++ b/plugins/module_utils/network/vyos/facts/nat/nat.py @@ -1,174 +1,175 @@ # -*- 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'") 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) 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) 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): # ← elif not if + 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["value"] + key = entry["address"] if key not in merged: - merged[key] = {"value": key} + 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 18c853fa..915dffc7 100644 --- a/plugins/module_utils/network/vyos/rm_templates/nat.py +++ b/plugins/module_utils/network/vyos/rm_templates/nat.py @@ -1,1142 +1,1243 @@ # -*- 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, ) 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": "cgnat_log_allocation", + "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": "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 %}", + # "compval": "external.range", + # "result": { + # "nat": { + # "cgnat": { + # "pool": { + # "external": [ + # { + # "name": "{{ name }}", + # "range": [ + # { + # "address": "{{ range }}", + # "seq": "{{ seq }}", + # }, + # ], + # }, + # ], + # }, + # }, + # }, + # }, + # }, { - "name": "cgnat_pool_external_range", + "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": [ - { + "external": { + "{{ name }}": { "name": "{{ name }}", "range": [ { - "value": "{{ range }}", + "address": "{{ range }}", "seq": "{{ seq }}", }, ], }, - ], + }, }, }, }, }, }, + # { + # "name": "cgnat_pool_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 }}", + # "compval": "nat.cgnat.pool.external", + # "result": { + # "nat": { + # "cgnat": { + # "pool": { + # "external": [ + # { + # "name": "{{ name }}", + # "external_port_range": "{{ range }}", + # }, + # ], + # }, + # }, + # }, + # }, + # }, { - "name": "cgnat_pool_external_port_range", + "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": [ - { + "external": { + "{{ name }}": { "name": "{{ name }}", "external_port_range": "{{ range }}", }, - ], + }, }, }, }, }, }, + # { + # "name": "cgnat_pool_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 }}", + # "per_user_limit": {"port": "{{ limit }}"}, + # }, + # ], + # }, + # }, + # }, + # }, + # }, { - "name": "cgnat_pool_external_per_user", + "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": [ - { + "external": { + "{{ name }}": { "name": "{{ name }}", "per_user_limit": {"port": "{{ limit }}"}, }, - ], + }, }, }, }, }, }, { "name": "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 }}", "range": ["{{ range }}"], }, ], }, }, }, }, }, { "name": "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 }}", "source": {"pool": "{{ pool }}"}, }, ], }, }, }, }, { "name": "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 }}", "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