Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces a new (WIP) vyos_nat resource module to the vyos.vyos Ansible collection, including its argspec, config implementation skeleton, facts gathering, and parsing/rendering templates, along with runtime/README/docs/changelog updates.
Changes:
- Added
vyos_natmodule entrypoint plus collection routing/docs/README/changelog entries. - Added NAT resource-module plumbing: argspec, config class, facts class, and rm_template parsers.
- Removed an old/unused
vrf.oldconfig implementation file.
Reviewed changes
Copilot reviewed 11 out of 15 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
plugins/modules/vyos_nat.py |
New module entrypoint + DOCUMENTATION/EXAMPLES/RETURN for vyos_nat. |
plugins/module_utils/network/vyos/argspec/nat/nat.py |
Adds NAT argspec (currently not shaped like other resource argspecs). |
plugins/module_utils/network/vyos/config/nat/nat.py |
Adds NAT ResourceModule implementation (currently contains a hard fail_json debug stub + commented code). |
plugins/module_utils/network/vyos/facts/nat/nat.py |
Adds NAT facts gathering/parsing via NatTemplate (needs normalization + docstring fixes). |
plugins/module_utils/network/vyos/rm_templates/nat.py |
Adds NAT regex/Jinja template parsers (key mismatches vs argspec + quoting issues). |
plugins/module_utils/network/vyos/facts/facts.py |
Registers nat as a new resource facts subset. |
meta/runtime.yml |
Adds short-name redirect nat -> vyos.vyos.vyos_nat. |
docs/vyos.vyos.vyos_nat_module.rst |
Generated docs for the new module. |
changelogs/fragments/t8323-vyos_nat.yml |
Changelog fragment for the new module (currently categorized as trivial). |
README.md |
Adds vyos_nat to the module list. |
plugins/module_utils/network/vyos/config/vrf/vrf.old |
Removes old VRF config implementation file. |
| 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) | ||
|
|
||
| self._module.fail_json(msg={"want": wantd, "have": haved}) | ||
|
|
||
| # wantd = self._ntp_list_to_dict(self.want) |
There was a problem hiding this comment.
generate_commands() currently calls self._module.fail_json(...), which will make the module fail for every state except parsed/gathered and prevents any configuration changes from being applied. Please remove this debug fail_json and implement the normal want/have comparison + command generation (similar to other resource modules like ntp_global).
| # wantd = self._ntp_list_to_dict(self.want) | ||
| # haved = self._ntp_list_to_dict(self.have) | ||
|
|
||
| # # if state is merged, merge want onto have and then compare | ||
| # if self.state == "merged": | ||
| # wantd = dict_merge(haved, wantd) | ||
|
|
||
| # # if state is deleted, empty out wantd and set haved to wantd | ||
| # if self.state == "deleted": | ||
| # haved = {k: v for k, v in haved.items() if k in wantd or not wantd} | ||
| # wantd = {} | ||
|
|
||
| # commandlist = self._commandlist(haved) | ||
| # servernames = self._servernames(haved) | ||
| # # removing the servername and commandlist from the list after deleting it from haved | ||
| # # iterate through the top-level items to delete | ||
| # for k, have in haved.items(): | ||
| # if k not in wantd: | ||
| # for hk, hval in have.items(): | ||
| # if hk == "allow_clients" and hk in commandlist: | ||
| # self.commands.append( | ||
| # self._tmplt.render({"": hk}, "allow_clients_delete", True), | ||
| # ) | ||
| # commandlist.remove(hk) | ||
| # elif hk == "listen_addresses" and hk in commandlist: | ||
| # self.commands.append( | ||
| # self._tmplt.render({"": hk}, "listen_addresses_delete", True), | ||
| # ) | ||
| # commandlist.remove(hk) | ||
| # elif hk == "server" and have["server"] in servernames: | ||
| # self._compareoverride(want={}, have=have) | ||
| # servernames.remove(have["server"]) | ||
| # # if everything is deleted add the delete command for {path} ntp | ||
| # # this should be equiv: servernames == [] and commandlist == ["server"]: | ||
| # if wantd == {} and haved != {}: | ||
| # self.commands.append( | ||
| # self._tmplt.render({}, "service_delete", True), | ||
| # ) | ||
|
|
||
| # # remove existing config for overridden and replaced | ||
| # # Getting the list of the server names from haved | ||
| # # to avoid the duplication of overridding/replacing the servers | ||
| # if self.state in ["overridden", "replaced"]: | ||
| # commandlist = self._commandlist(haved) | ||
| # servernames = self._servernames(haved) | ||
|
|
There was a problem hiding this comment.
This file still contains a large block of commented-out NTP-specific code. Keeping unrelated commented code makes the NAT resource module harder to maintain and review; please remove it (or replace it with NAT-specific command-generation logic/tests) before merging.
|
|
||
| argument_spec = { | ||
| "config": { | ||
| "type": "dict", | ||
| "nat": { | ||
| "type": "dict", | ||
| "options": { | ||
| "cgnat": { | ||
| "type": "dict", | ||
| "options": { | ||
| "log_allocation": { | ||
| "type": "bool", | ||
| }, | ||
| "pool": { | ||
| "type": "dict", | ||
| "options": { |
There was a problem hiding this comment.
The NAT argspec structure under config is not using the standard Ansible argument-spec shape (config: {type: dict, options: {...}}). As written, keys like nat, nat64, and nat66 are not under an options dict, which will prevent Ansible from validating/accepting those suboptions correctly. Please restructure config to use an options mapping (see argspec/ntp_global/ntp_global.py for the expected pattern).
| EXAMPLES = """ | ||
| # Using merged | ||
| - name: Merge NAT source rule | ||
| vyos.vyos.vyos_nat: | ||
| config: | ||
| source: | ||
| rule: | ||
| - id: 100 | ||
| description: "Outbound masquerade" | ||
| state: merged | ||
|
|
||
| # Using gathered | ||
| - name: Gather NAT config | ||
| vyos.vyos.vyos_nat: | ||
| state: gathered | ||
|
|
||
| # Using deleted | ||
| - name: Delete NAT config | ||
| vyos.vyos.vyos_nat: | ||
| state: deleted | ||
|
|
||
| # Using replaced | ||
| - name: Replace NAT config | ||
| vyos.vyos.vyos_nat: | ||
| config: | ||
| source: | ||
| rule: | ||
| - id: 100 | ||
| description: "Replaced rule" | ||
| state: replaced | ||
|
|
||
| # Using parsed | ||
| - name: Parse NAT config | ||
| vyos.vyos.vyos_nat: | ||
| running_config: "{{ lookup('file', './nat_config.cfg') }}" | ||
| state: parsed | ||
|
|
||
| # Using rendered | ||
| - name: Render NAT config offline | ||
| vyos.vyos.vyos_nat: | ||
| config: | ||
| source: | ||
| rule: | ||
| - id: 100 | ||
| description: "Rendered rule" | ||
| state: rendered | ||
| """ |
There was a problem hiding this comment.
The module DOCUMENTATION/EXAMPLES are inconsistent with the argspec: DOCUMENTATION defines config.nat.* (and config.nat64/nat66), but the EXAMPLES use config: { source: ... } at the top level. This will confuse users and (depending on the final argspec) may not work at all; please update examples and/or the DOCUMENTATION options layout to match the actual module parameters.
| "result": { | ||
| "{{ nat }}": { | ||
| "cgnat": { | ||
| "pool": { | ||
| "external": [ | ||
| { | ||
| "name": "{{ name }}", | ||
| "ranges": ["{{ range }}"], | ||
| "seq": "{{ seq }}", | ||
| }, | ||
| ], | ||
| }, | ||
| }, |
There was a problem hiding this comment.
The external CGNAT pool parser populates ranges (plural) and a seq field in the parsed result, but the module argspec/DOCUMENTATION defines the field as range and does not define seq. This mismatch will cause validate_config() to drop/empty the parsed data (and can break idempotency). Please align the parsed result keys with the argspec (and only emit fields that exist in the argspec).
| author: | ||
| - Evgeny Molotkov (@omnom62) | ||
| notes: | ||
| - Tested against VyOS 1.3.8, 1.4.2, the upcoming 1.5, and the rolling release of spring 2025 |
There was a problem hiding this comment.
In notes, the first bullet does not end with a period. Option/notes text in module docs should be complete sentences ending with a period for consistency with the rest of the collection documentation.
| - Tested against VyOS 1.3.8, 1.4.2, the upcoming 1.5, and the rolling release of spring 2025 | |
| - Tested against VyOS 1.3.8, 1.4.2, the upcoming 1.5, and the rolling release of spring 2025. |
| # -*- coding: utf-8 -*- | ||
| from __future__ import absolute_import, division, print_function | ||
|
|
||
|
|
||
| __metaclass__ = type | ||
|
|
There was a problem hiding this comment.
rm_templates files in this collection typically include the standard copyright header and a module-level docstring describing the template purpose (e.g. rm_templates/ntp_global.py, rm_templates/route_maps.py). This new template file is missing that header/docstring, which makes it inconsistent and harder to maintain.
| \s+(?P<description>.+) | ||
| $""", | ||
| re.VERBOSE, | ||
| ), | ||
| "setval": "{{ nat }} {{ type }} rule {{ rule }} description {{ description }}", | ||
| "result": { | ||
| "{{ nat }}": { | ||
| "{{ type }}": { | ||
| "rule": { | ||
| "{{ rule }}": {"description": "{{ description }}"}, |
There was a problem hiding this comment.
The NAT rule description command rendering does not quote the description value (... description {{ description }}). This will break for descriptions containing spaces/special characters and is inconsistent with other templates that render descriptions as single-quoted strings. Please ensure the rendered command wraps the description in quotes and that parsing/rendering round-trips correctly.
| \s+(?P<description>.+) | |
| $""", | |
| re.VERBOSE, | |
| ), | |
| "setval": "{{ nat }} {{ type }} rule {{ rule }} description {{ description }}", | |
| "result": { | |
| "{{ nat }}": { | |
| "{{ type }}": { | |
| "rule": { | |
| "{{ rule }}": {"description": "{{ description }}"}, | |
| \s+(?:'(?P<description>[^']*)'|(?P<description_unquoted>.+)) | |
| $""", | |
| re.VERBOSE, | |
| ), | |
| "setval": "{{ nat }} {{ type }} rule {{ rule }} description '{{ description }}'", | |
| "result": { | |
| "{{ nat }}": { | |
| "{{ type }}": { | |
| "rule": { | |
| "{{ rule }}": {"description": "{{ description | default(description_unquoted) }}"}, |
| @@ -0,0 +1,3 @@ | |||
| --- | |||
| trivial: | |||
There was a problem hiding this comment.
This changelog fragment is marked as trivial, but adding a new user-facing module is typically a minor_changes entry in this collection (e.g. changelogs/fragments/t6838_vrf.yml). Please move this entry under minor_changes so it is surfaced correctly in release notes.
| trivial: | |
| minor_changes: |
| def main(): | ||
| """ | ||
| Main entry point for module execution | ||
|
|
||
| :returns: the result form module invocation | ||
| """ | ||
| module = AnsibleModule( | ||
| argument_spec=NatArgs.argument_spec, | ||
| mutually_exclusive=[["config", "running_config"]], | ||
| required_if=[ | ||
| ["state", "merged", ["config"]], | ||
| ["state", "replaced", ["config"]], | ||
| ["state", "overridden", ["config"]], | ||
| ["state", "rendered", ["config"]], | ||
| ["state", "parsed", ["running_config"]], | ||
| ], | ||
| supports_check_mode=True, | ||
| ) | ||
|
|
||
| result = Nat(module).execute_module() | ||
| module.exit_json(**result) | ||
|
|
There was a problem hiding this comment.
There are comprehensive unit tests for existing VyOS resource modules under tests/unit/modules/network/vyos/, but this PR adds a new vyos_nat module without any corresponding unit tests. Please add at least basic unit coverage for merged/deleted/rendered/parsed/gathered behavior and a couple of representative NAT rule renderings/parses to prevent regressions.
Change Summary
Types of changes
Related Task(s)
Related PR(s)
Component(s) name
Proposed changes
How to test
Test results
Tested against VyOS versions:
Checklist:
changelogs/fragmentsto describe the changes