From 4b0aeede695500e56ffc7c569dd41411cafba7a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Jacquin?= <1536771+remyj38@users.noreply.github.com> Date: Fri, 16 Jan 2026 21:05:43 +0100 Subject: [PATCH] feat(nmcli): Add support for IPv6 routing rules (#11413) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(nmcli): Add support for IPv6 routing rules Closes #7094 Signed-off-by: Rémy Jacquin * Add changelog fragment Signed-off-by: Rémy Jacquin * Fixing doc Signed-off-by: Rémy Jacquin * Add issue link to changelog fragment Co-authored-by: Felix Fontein * Fix version Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com> --------- Signed-off-by: Rémy Jacquin Co-authored-by: Felix Fontein Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com> --- .../fragments/11413-nmcli-routing-rules6.yml | 2 + plugins/modules/nmcli.py | 9 +++++ tests/unit/plugins/modules/test_nmcli.py | 39 ++++++++++++++++++- 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 changelogs/fragments/11413-nmcli-routing-rules6.yml diff --git a/changelogs/fragments/11413-nmcli-routing-rules6.yml b/changelogs/fragments/11413-nmcli-routing-rules6.yml new file mode 100644 index 0000000000..649c8203ed --- /dev/null +++ b/changelogs/fragments/11413-nmcli-routing-rules6.yml @@ -0,0 +1,2 @@ +minor_changes: + - nmcli - add support for IPv6 routing rules (https://github.com/ansible-collections/community.general/issues/7094, https://github.com/ansible-collections/community.general/pull/11413). diff --git a/plugins/modules/nmcli.py b/plugins/modules/nmcli.py index bb10a53d1f..9ea2a41009 100644 --- a/plugins/modules/nmcli.py +++ b/plugins/modules/nmcli.py @@ -226,6 +226,12 @@ options: type: list elements: str version_added: 3.3.0 + routing_rules6: + description: + - Is the same as in an C(ip rule add) command, except always requires specifying a priority. + type: list + elements: str + version_added: 12.3.0 never_default4: description: - Set as default route. @@ -1765,6 +1771,7 @@ class Nmcli: self.routes6 = module.params["routes6"] self.routes6_extended = module.params["routes6_extended"] self.route_metric6 = module.params["route_metric6"] + self.routing_rules6 = module.params["routing_rules6"] self.dns6 = module.params["dns6"] self.dns6_search = module.params["dns6_search"] self.dns6_options = module.params["dns6_options"] @@ -1902,6 +1909,7 @@ class Nmcli: "ipv6.ignore-auto-routes": self.gw6_ignore_auto, "ipv6.routes": self.enforce_routes_format(self.routes6, self.routes6_extended), "ipv6.route-metric": self.route_metric6, + "ipv6.routing-rules": self.routing_rules6, "ipv6.method": self.ipv6_method, "ipv6.ip6-privacy": self.ip_privacy6, "ipv6.addr-gen-mode": self.addr_gen_mode6, @@ -2788,6 +2796,7 @@ def create_module() -> AnsibleModule: ), ), route_metric6=dict(type="int"), + routing_rules6=dict(type="list", elements="str"), method6=dict(type="str", choices=["ignore", "auto", "dhcp", "link-local", "manual", "shared", "disabled"]), ip_privacy6=dict(type="str", choices=["disabled", "prefer-public-addr", "prefer-temp-addr", "unknown"]), addr_gen_mode6=dict(type="str", choices=["default", "default-or-eui64", "eui64", "stable-privacy"]), diff --git a/tests/unit/plugins/modules/test_nmcli.py b/tests/unit/plugins/modules/test_nmcli.py index c10ad68760..16af663e4f 100644 --- a/tests/unit/plugins/modules/test_nmcli.py +++ b/tests/unit/plugins/modules/test_nmcli.py @@ -167,7 +167,7 @@ TESTCASE_GENERIC_DIFF_CHECK = [ }, ] -TESTCASE_GENERIC_MODIFY_ROUTING_RULES = [ +TESTCASE_GENERIC_MODIFY_ROUTING_RULES4 = [ { "type": "generic", "conn_name": "non_existent_nw_device", @@ -180,6 +180,19 @@ TESTCASE_GENERIC_MODIFY_ROUTING_RULES = [ }, ] +TESTCASE_GENERIC_MODIFY_ROUTING_RULES6 = [ + { + "type": "generic", + "conn_name": "non_existent_nw_device", + "ifname": "generic_non_existant", + "ip6": "fd00::10/24", + "gw6": "fd00::1", + "routing_rules6": ["priority 5 from fd00::/24 table 5000", "priority 10 from fd01::/24 table 5001"], + "state": "present", + "_ansible_check_mode": False, + }, +] + TESTCASE_GENERIC_MODIFY_ROUTING_RULES_SHOW_OUTPUT = """\ connection.id: non_existent_nw_device connection.interface-name: generic_non_existant @@ -2211,7 +2224,7 @@ def test_generic_connection_unchanged(mocked_generic_connection_unchanged, capfd @pytest.mark.parametrize( - "patch_ansible_module", TESTCASE_GENERIC_MODIFY_ROUTING_RULES, indirect=["patch_ansible_module"] + "patch_ansible_module", TESTCASE_GENERIC_MODIFY_ROUTING_RULES4, indirect=["patch_ansible_module"] ) def test_generic_connection_modify_routing_rules4(mocked_generic_connection_create, capfd): """ @@ -2232,6 +2245,28 @@ def test_generic_connection_modify_routing_rules4(mocked_generic_connection_crea assert results["changed"] +@pytest.mark.parametrize( + "patch_ansible_module", TESTCASE_GENERIC_MODIFY_ROUTING_RULES6, indirect=["patch_ansible_module"] +) +def test_generic_connection_modify_routing_rules6(mocked_generic_connection_create, capfd): + """ + Test : Generic connection modified with routing-rules6 + """ + with pytest.raises(SystemExit): + nmcli.main() + + assert nmcli.Nmcli.execute_command.call_count == 1 + arg_list = nmcli.Nmcli.execute_command.call_args_list + args, kwargs = arg_list[0] + + assert "ipv6.routing-rules" in args[0] + + out, err = capfd.readouterr() + results = json.loads(out) + assert not results.get("failed") + assert results["changed"] + + @pytest.mark.parametrize("patch_ansible_module", TESTCASE_GENERIC_DNS4_SEARCH, indirect=["patch_ansible_module"]) def test_generic_connection_create_dns_search(mocked_generic_connection_create, capfd): """