From e539028c2f9d062e7684abda5dd32affd92e9f08 Mon Sep 17 00:00:00 2001 From: lucaelin Date: Mon, 23 Feb 2026 13:25:35 +0100 Subject: add commit-confirm options to vyos_config (#229) * added vyos_config confirm options * fix: update commit comment to match main * fix: documentation example for automatic confirm * fix: add tests for commit confirm and confirm_timeout options * fix: add config_confirm changelog fragment * fix: lint missing diff parameter for edit_config * fix: add commit confirm integration test --------- Co-authored-by: omnom62 <75066712+omnom62@users.noreply.github.com> --- changelogs/fragments/config_confirm.yml | 3 ++ plugins/cliconf/vyos.py | 23 +++++++---- plugins/module_utils/network/vyos/vyos.py | 6 ++- plugins/modules/vyos_config.py | 33 +++++++++++++++- .../targets/vyos_config/tests/cli/confirm.yaml | 44 ++++++++++++++++++++++ .../unit/modules/network/vyos/test_vyos_config.py | 38 +++++++++++++++++++ 6 files changed, 137 insertions(+), 10 deletions(-) create mode 100644 changelogs/fragments/config_confirm.yml create mode 100644 tests/integration/targets/vyos_config/tests/cli/confirm.yaml diff --git a/changelogs/fragments/config_confirm.yml b/changelogs/fragments/config_confirm.yml new file mode 100644 index 00000000..7c0565af --- /dev/null +++ b/changelogs/fragments/config_confirm.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - vyos_config - add support for commit-confirm workflows (automatic/manual) including configurable timeout diff --git a/plugins/cliconf/vyos.py b/plugins/cliconf/vyos.py index c35ff1ec..17fcdf24 100644 --- a/plugins/cliconf/vyos.py +++ b/plugins/cliconf/vyos.py @@ -122,7 +122,9 @@ class Cliconf(CliconfBase): out = self.send_command(command) return out - def edit_config(self, candidate=None, commit=True, replace=None, comment=None): + def edit_config( + self, candidate=None, commit=True, replace=None, diff=False, comment=None, confirm=None + ): resp = {} operations = self.get_device_operations() self.check_edit_config_capability(operations, candidate, commit, replace, comment) @@ -143,7 +145,7 @@ class Cliconf(CliconfBase): if diff_config: if commit: try: - self.commit(comment) + self.commit(comment, confirm) except AnsibleConnectionFailure as e: msg = "commit failed: %s" % e.message self.discard_changes() @@ -191,12 +193,19 @@ class Cliconf(CliconfBase): check_all=check_all, ) - def commit(self, comment=None): - if comment: - command = 'commit comment "{0}"'.format(comment) + def commit(self, comment=None, confirm=None): + if confirm: + if comment: + command = 'commit-confirm {0} comment "{1}"'.format(confirm, comment) + else: + command = 'commit-confirm {0}'.format(confirm) + self.send_command(command, "Proceed?", "\n") else: - command = "commit" - self.send_command(command) + if comment: + command = 'commit comment "{0}"'.format(comment) + else: + command = "commit" + self.send_command(command) def discard_changes(self): self.send_command("exit discard") diff --git a/plugins/module_utils/network/vyos/vyos.py b/plugins/module_utils/network/vyos/vyos.py index 6bd8daee..4987d6b0 100644 --- a/plugins/module_utils/network/vyos/vyos.py +++ b/plugins/module_utils/network/vyos/vyos.py @@ -96,11 +96,13 @@ def run_commands(module, commands, check_rc=True): return response -def load_config(module, commands, commit=False, comment=None): +def load_config(module, commands, commit=False, comment=None, confirm=None): connection = get_connection(module) try: - response = connection.edit_config(candidate=commands, commit=commit, comment=comment) + response = connection.edit_config( + candidate=commands, commit=commit, comment=comment, confirm=confirm + ) except ConnectionError as exc: module.fail_json(msg=to_text(exc, errors="surrogate_then_replace")) diff --git a/plugins/modules/vyos_config.py b/plugins/modules/vyos_config.py index eeb6bc44..a9774638 100644 --- a/plugins/modules/vyos_config.py +++ b/plugins/modules/vyos_config.py @@ -84,6 +84,25 @@ options: is ignored. default: configured by vyos_config type: str + confirm: + description: + - The C(confirm) argument will tell vyos to revert to the previous configuration + if not explicitly confirmed after applying the new config. When set to C(automatic) + this module will automatically confirm the configuration, if the current session + remains working with the new config. When set to C(manual), this module does + not issue the confirmation itself. + type: str + default: none + choices: + - automatic + - manual + - none + confirm_timeout: + description: + - Minutes to wait for confirmation before reverting the configuration. Does + not apply when C(confirm) is set to C(none) . + type: int + default: 10 config: description: - The C(config) argument specifies the base configuration to use to compare against @@ -142,6 +161,11 @@ EXAMPLES = """ vyos.vyos.vyos_config: src: vyos_template.j2 +- name: revert after ten minutes, if connection is lost + vyos.vyos.vyos_config: + src: vyos_template.j2 + confirm: automatic + - name: for idempotency, use full-form commands vyos.vyos.vyos_config: lines: @@ -310,10 +334,15 @@ def run(module, result): commit = not module.check_mode comment = module.params["comment"] + confirm = None + if module.params["confirm"] == "automatic" or module.params["confirm"] == "manual": + confirm = module.params["confirm_timeout"] diff = None if commands: - diff = load_config(module, commands, commit=commit, comment=comment) + diff = load_config(module, commands, commit=commit, comment=comment, confirm=confirm) + if module.params["confirm"] == "automatic": + run_commands(module, ["configure", "confirm", "exit"]) if result.get("filtered"): result["warnings"].append( @@ -333,6 +362,8 @@ def main(): lines=dict(type="list", elements="str"), match=dict(default="line", choices=["line", "none"]), comment=dict(default=DEFAULT_COMMENT), + confirm=dict(choices=["automatic", "manual", "none"], default='none'), + confirm_timeout=dict(type="int", default=10), config=dict(), backup=dict(type="bool", default=False), backup_options=dict(type="dict", options=backup_spec), diff --git a/tests/integration/targets/vyos_config/tests/cli/confirm.yaml b/tests/integration/targets/vyos_config/tests/cli/confirm.yaml new file mode 100644 index 00000000..73674a17 --- /dev/null +++ b/tests/integration/targets/vyos_config/tests/cli/confirm.yaml @@ -0,0 +1,44 @@ +--- +- debug: msg="START cli/confirm.yaml on connection={{ ansible_connection }}" + +- name: setup + vyos.vyos.vyos_config: + lines: set system host-name {{ inventory_hostname_short }} + match: none + +- name: configure with confirm (manual) + register: result + vyos.vyos.vyos_config: + lines: set system host-name foo + comment: confirm manual test + confirm: manual + confirm_timeout: 1 + +- assert: + that: + - result.changed == true + - "'set system host-name foo' in result.commands" + +- name: verify hostname changed to foo + register: hostname_after + vyos.vyos.vyos_command: + commands: show host name + +- assert: + that: + - "'foo' in hostname_after.stdout[0]" + +- name: wait until config auto-reverts (no confirmation) + register: hostname_reverted + vyos.vyos.vyos_command: + commands: show host name + retries: 18 + delay: 5 + until: inventory_hostname_short in hostname_reverted.stdout[0] + +- name: teardown + vyos.vyos.vyos_config: + lines: set system host-name {{ inventory_hostname_short }} + match: none + +- debug: msg="END cli/confirm.yaml on connection={{ ansible_connection }}" diff --git a/tests/unit/modules/network/vyos/test_vyos_config.py b/tests/unit/modules/network/vyos/test_vyos_config.py index 4f1cac6a..601971cd 100644 --- a/tests/unit/modules/network/vyos/test_vyos_config.py +++ b/tests/unit/modules/network/vyos/test_vyos_config.py @@ -140,3 +140,41 @@ class TestVyosConfigModule(TestVyosModule): return_value=self.cliconf_obj.get_diff(candidate, None, diff_match="none"), ) self.execute_module(changed=True, commands=lines, sort=False) + + def test_vyos_config_confirm_automatic(self): + src = load_fixture("vyos_config_src.cfg") + confirm_timeout = 7 + set_module_args(dict(src=src, confirm="automatic", confirm_timeout=confirm_timeout)) + candidate = "\n".join(self.module.format_commands(src.splitlines())) + commands = [ + "set system host-name foo", + "delete interfaces ethernet eth0 address", + ] + self.conn.get_diff = MagicMock( + return_value=self.cliconf_obj.get_diff(candidate, self.running_config), + ) + + self.execute_module(changed=True, commands=commands) + + self.assertEqual(self.load_config.call_args[1]["confirm"], confirm_timeout) + self.run_commands.assert_called_once() + self.assertEqual( + ["configure", "confirm", "exit"], + self.run_commands.call_args[0][1], + ) + + def test_vyos_config_confirm_manual(self): + lines = [ + "set system host-name foo", + ] + confirm_timeout = 12 + set_module_args(dict(lines=lines, confirm="manual", confirm_timeout=confirm_timeout)) + candidate = "\n".join(lines) + self.conn.get_diff = MagicMock( + return_value=self.cliconf_obj.get_diff(candidate, self.running_config), + ) + + self.execute_module(changed=True, commands=lines) + + self.assertEqual(self.load_config.call_args[1]["confirm"], confirm_timeout) + self.run_commands.assert_not_called() -- cgit v1.2.3