diff --git a/tests/unit/modules/network/vyos/test_vyos_config.py b/tests/unit/modules/network/vyos/test_vyos_config.py index c04d4809..d6a75f1d 100644 --- a/tests/unit/modules/network/vyos/test_vyos_config.py +++ b/tests/unit/modules/network/vyos/test_vyos_config.py @@ -1,360 +1,385 @@ # (c) 2016 Red Hat Inc. # # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # Make coding more python3-ish from __future__ import absolute_import, division, print_function + __metaclass__ = type from unittest.mock import MagicMock, patch from ansible_collections.vyos.vyos.plugins.cliconf.vyos import Cliconf from ansible_collections.vyos.vyos.plugins.modules import vyos_config from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args from .vyos_module import TestVyosModule, load_fixture class TestVyosConfigModule(TestVyosModule): module = vyos_config def setUp(self): super(TestVyosConfigModule, self).setUp() self.mock_get_config = patch( "ansible_collections.vyos.vyos.plugins.modules.vyos_config.get_config", ) self.get_config = self.mock_get_config.start() self.mock_load_config = patch( "ansible_collections.vyos.vyos.plugins.modules.vyos_config.load_config", ) self.load_config = self.mock_load_config.start() self.mock_run_commands = patch( "ansible_collections.vyos.vyos.plugins.modules.vyos_config.run_commands", ) self.run_commands = self.mock_run_commands.start() self.mock_get_connection = patch( "ansible_collections.vyos.vyos.plugins.modules.vyos_config.get_connection", ) self.get_connection = self.mock_get_connection.start() self.mock_copy_file = patch( "ansible_collections.vyos.vyos.plugins.modules.vyos_config.copy_file", ) self.copy_file = self.mock_copy_file.start() self.cliconf_obj = Cliconf(MagicMock()) self.running_config = load_fixture("vyos_config_config.cfg") self.conn = self.get_connection() self.conn.edit_config = MagicMock() self.running_config = load_fixture("vyos_config_config.cfg") def tearDown(self): super(TestVyosConfigModule, self).tearDown() self.mock_get_config.stop() self.mock_load_config.stop() self.mock_run_commands.stop() self.mock_get_connection.stop() self.mock_copy_file.stop() def load_fixtures(self, commands=None, filename=None): config_file = "vyos_config_config.cfg" self.get_config.return_value = load_fixture(config_file) self.load_config.return_value = None def test_vyos_config_unchanged(self): src = load_fixture("vyos_config_config.cfg") self.conn.get_diff = MagicMock(return_value=self.cliconf_obj.get_diff(src, src)) set_module_args(dict(src=src)) self.execute_module() def test_vyos_config_src(self): src = load_fixture("vyos_config_src.cfg") set_module_args(dict(src=src)) 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) def test_vyos_config_src_brackets(self): src = load_fixture("vyos_config_src_brackets.cfg") set_module_args(dict(src=src)) commands = [ "set interfaces ethernet eth0 address 10.10.10.10/24", "set policy route testroute rule 1 set table 10", "set system host-name foo", ] self.conn.get_diff = MagicMock(side_effect=self.cliconf_obj.get_diff) self.execute_module(changed=True, commands=commands) def test_vyos_config_backup(self): set_module_args(dict(backup=True)) result = self.execute_module() self.assertIn("__backup__", result) def test_vyos_config_lines(self): commands = ["set system host-name foo"] set_module_args(dict(lines=commands)) candidate = "\n".join(commands) self.conn.get_diff = MagicMock( return_value=self.cliconf_obj.get_diff(candidate, self.running_config), ) self.execute_module(changed=True, commands=commands) def test_vyos_config_config(self): config = "set system host-name localhost" new_config = ["set system host-name router"] set_module_args(dict(lines=new_config, config=config)) candidate = "\n".join(new_config) self.conn.get_diff = MagicMock(return_value=self.cliconf_obj.get_diff(candidate, config)) self.execute_module(changed=True, commands=new_config) def test_vyos_config_match_none(self): lines = [ "set system interfaces ethernet eth0 address 1.2.3.4/24", "set system interfaces ethernet eth0 description test string", ] set_module_args(dict(lines=lines, match="none")) candidate = "\n".join(lines) self.conn.get_diff = MagicMock( 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() # -- replace=config (T6837, cisco.iosxr.iosxr_config replace=config analogue) -- def test_vyos_config_replace_config_requires_src(self): """replace=config without src must fail argument validation, not run.""" set_module_args(dict(replace="config")) result = self.execute_module(failed=True) self.assertIn("src", result["msg"]) def test_vyos_config_replace_config_rejects_lines_only(self): """replace=config with only lines (no src) must fail -- required_if demands src regardless of what else is set. """ set_module_args(dict(replace="config", lines=["set system host-name foo"])) self.execute_module(failed=True) def test_vyos_config_replace_config_rejects_lines_and_src_together(self): """lines/src remain mutually exclusive regardless of replace -- this is the pre-existing constraint, unaffected by replace=config.""" set_module_args( dict( replace="config", src="system {\n host-name router\n}\n", lines=["set system host-name foo"], ), ) self.execute_module(failed=True) def test_vyos_config_replace_config_pushes_and_loads(self): """replace=config with a real change: copies the candidate to a fixed remote path, issues a single `load ` command, and reports the device's own diff verbatim -- not an itemized set/delete list. """ src = "interfaces {\n ethernet eth0 {\n address dhcp\n }\n}\n" set_module_args(dict(replace="config", src=src)) self.load_config.side_effect = lambda *a, **kw: ( "[edit interfaces]\n+ethernet eth0 {\n+ address dhcp\n+}" ) result = self.execute_module(changed=True) self.assertEqual(result["commands"], ["load /tmp/ansible_vyos_replace.cfg"]) self.copy_file.assert_called_once() # positional call: copy_file(module, local_path, remote_path, proto) self.assertEqual(self.copy_file.call_args[0][2], "/tmp/ansible_vyos_replace.cfg") self.assertEqual(self.copy_file.call_args[0][3], "scp") self.assertEqual( self.load_config.call_args[0][1], ["load /tmp/ansible_vyos_replace.cfg"], ) def test_vyos_config_replace_config_noop(self): """replace=config with load_config() returning falsy (VyOS's own `compare` reported no changes) must report changed=False, not unconditionally True. """ src = "system {\n host-name router\n}\n" set_module_args(dict(replace="config", src=src)) self.load_config.return_value = None result = self.execute_module(changed=False) self.assertEqual(result["commands"], ["load /tmp/ansible_vyos_replace.cfg"]) def test_vyos_config_replace_config_check_mode(self): """Under check_mode, commit=False must be passed through to load_config() -- the candidate is still copied/loaded for an accurate compare-based preview diff, but nothing is committed. """ src = "system {\n host-name router\n}\n" set_module_args(dict(replace="config", src=src, _ansible_check_mode=True)) self.load_config.side_effect = lambda *a, **kw: ( "[edit system]\n-host-name foo\n+host-name router" ) self.execute_module(changed=True) self.assertEqual(self.load_config.call_args[1]["commit"], False) def test_vyos_config_replace_config_confirm_automatic(self): src = "system {\n host-name router\n}\n" confirm_timeout = 9 set_module_args( dict( replace="config", src=src, confirm="automatic", confirm_timeout=confirm_timeout, ), ) self.load_config.side_effect = lambda *a, **kw: ( "[edit system]\n-host-name foo\n+host-name router" ) self.execute_module(changed=True) 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_replace_config_diff(self): """With --diff, result['diff']['prepared'] must carry VyOS's own compare() output verbatim -- not an itemized command list, since none is computed in this mode. """ src = "system {\n host-name router\n}\n" set_module_args(dict(replace="config", src=src, _ansible_diff=True)) raw_compare = "[edit system]\n-host-name foo\n+host-name router" self.load_config.side_effect = lambda *a, **kw: raw_compare result = self.execute_module(changed=True) self.assertEqual(result["diff"]["prepared"], raw_compare) def test_vyos_config_replace_config_does_not_use_line_diff_path(self): """replace=config must never call connection.get_diff() -- that path (and match/allow_password_change) is specific to replace=line and is documented as ignored under replace=config. """ src = "system {\n host-name router\n}\n" set_module_args(dict(replace="config", src=src, match="none")) self.load_config.side_effect = lambda *a, **kw: ( "[edit system]\n-host-name foo\n+host-name router" ) self.execute_module(changed=True) self.conn.get_diff.assert_not_called() def test_vyos_config_replace_config_confirm_automatic_check_mode_no_confirm_sent(self): """Regression guard: confirm=automatic must not send the configure/confirm/exit sequence under check_mode, even when a real diff is present -- nothing was actually committed to confirm. """ src = "system {\n host-name router\n}\n" set_module_args( dict( replace="config", src=src, confirm="automatic", _ansible_check_mode=True, ), ) self.load_config.side_effect = lambda *a, **kw: ( "[edit system]\n-host-name foo\n+host-name router" ) self.execute_module(changed=True) self.run_commands.assert_not_called() def test_vyos_config_replace_config_confirm_automatic_noop_no_confirm_sent(self): """Regression guard: confirm=automatic must not send the configure/confirm/exit sequence when load_config() reports no diff (VyOS's own compare() found nothing to commit) -- there is nothing pending to confirm. """ src = "system {\n host-name router\n}\n" set_module_args(dict(replace="config", src=src, confirm="automatic")) self.load_config.return_value = None self.execute_module(changed=False) self.run_commands.assert_not_called() def test_vyos_config_replace_line_default_unaffected(self): """Regression guard: default replace='line' must behave identically to the pre-patch module -- copy_file() must never be invoked. """ commands = ["set system host-name foo"] set_module_args(dict(lines=commands)) candidate = "\n".join(commands) self.conn.get_diff = MagicMock( return_value=self.cliconf_obj.get_diff(candidate, self.running_config), ) self.execute_module(changed=True, commands=commands) self.copy_file.assert_not_called() + + def test_sanitize_config_filters_password_delete_lines(self): + """ + sanitize_config()/PASSWORD_NEEDLE must filter 'delete ... password' + lines the same way it filters 'set ... password' lines, since + replace=config can generate deletes for password config the + candidate omits. + """ + result = {} + commands = [ + "set system host-name foo", + "delete system login user admin authentication encrypted-password", + "set system login user admin authentication plaintext-password 'secret'", + ] + vyos_config.sanitize_config(commands, result, allow="none") + self.assertIn( + "delete system login user admin authentication encrypted-password", + result["filtered"], + ) + self.assertIn( + "set system login user admin authentication plaintext-password 'secret'", + result["filtered"], + ) + self.assertNotIn("set system host-name foo", result["filtered"])