diff --git a/docs/changelog/2024/September.rst b/docs/changelog/2024/September.rst new file mode 100644 index 00000000..2806f924 --- /dev/null +++ b/docs/changelog/2024/September.rst @@ -0,0 +1,45 @@ +September 2024 +========== + +September 24 - Unicon v24.9 +------------------------ + + + +.. csv-table:: Module Versions + :header: "Modules", "Versions" + + ``unicon.plugins``, v24.9 + ``unicon``, v24.9 + + + + +Changelogs +^^^^^^^^^^ +-------------------------------------------------------------------------------- + Fix +-------------------------------------------------------------------------------- + +* backend.spawn0 + * Modified RawSpawn + * Added check for when a decode error occurs n amount of times + +* unicon + * topology + * Fixed logic for proxy connection. + * sshtunnel + * Added -o EnableEscapeCommandline=yes to ssh-options. + +* unicon.bases + * Added message argument to log_service_call + + +-------------------------------------------------------------------------------- + New +-------------------------------------------------------------------------------- + +* generic + * Added upwards error propagation for decode errors + + diff --git a/docs/changelog/2024/august.rst b/docs/changelog/2024/august.rst index 6960f306..fb03cbd1 100644 --- a/docs/changelog/2024/august.rst +++ b/docs/changelog/2024/august.rst @@ -36,57 +36,3 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ --------------------------------------------------------------------------------- - Fix --------------------------------------------------------------------------------- - -* unicon.bases - * Added message argument to log_service_call - -* unicon.statemachine - * Modified Exception handling, propagate authentication failures - -* unicon - * topology - * Fixed logic for proxy connection. - * sshtunnel - * Added -o EnableEscapeCommandline=yes to ssh-options. - -* unicon.eal.backend - * Modified telnet backend - * improved option negotiation - * Added informational RTT log message - - --------------------------------------------------------------------------------- - New --------------------------------------------------------------------------------- - -* unicon.adapter - * Modified topology adapter to support enxr - -* unicon.core.errors - * Add new exception LearnTokenError - -* unicon.bases - * Update exception handling to raise LearnTokenError without closing connection - - --------------------------------------------------------------------------------- - New --------------------------------------------------------------------------------- - -* iosxe - * Modified Rommon service - * Allowing for a config-register parameter to the rommon service - - --------------------------------------------------------------------------------- - Fix --------------------------------------------------------------------------------- - -* unicon.plugins.generic - * Modified password_handler - * Have it check for tacacs_password first - - diff --git a/docs/changelog/index.rst b/docs/changelog/index.rst index aaf579cd..e194bba5 100644 --- a/docs/changelog/index.rst +++ b/docs/changelog/index.rst @@ -4,6 +4,7 @@ Changelog .. toctree:: :maxdepth: 2 + 2024/September 2024/august 2024/july 2024/june diff --git a/docs/changelog_plugins/2024/September.rst b/docs/changelog_plugins/2024/September.rst new file mode 100644 index 00000000..a74c4d1d --- /dev/null +++ b/docs/changelog_plugins/2024/September.rst @@ -0,0 +1,42 @@ +September 2024 +========== + +September 24 - Unicon.Plugins v24.9 +------------------------ + + + +.. csv-table:: Module Versions + :header: "Modules", "Versions" + + ``unicon.plugins``, v24.9 + ``unicon``, v24.9 + + + + +Changelogs +^^^^^^^^^^ +-------------------------------------------------------------------------------- + Fix +-------------------------------------------------------------------------------- + +* iosxr + * Added support for APIC patterns + +* iosxe + * Update config prompt pattern to support CA cert map + +* generic + * Update execute() service log message to include device alias + * Add parse method to bash_console context manager with abstraction fallback to linux os + + +-------------------------------------------------------------------------------- + Add +-------------------------------------------------------------------------------- + +* apic plugin + * Added Regex in post_service in Execute to remove extra junk values. + + diff --git a/docs/changelog_plugins/2024/august.rst b/docs/changelog_plugins/2024/august.rst index a32becbb..66a7ef25 100644 --- a/docs/changelog_plugins/2024/august.rst +++ b/docs/changelog_plugins/2024/august.rst @@ -36,32 +36,3 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ --------------------------------------------------------------------------------- - Add --------------------------------------------------------------------------------- - -* pid_tokens - * add pid entry for ir1800 device - - --------------------------------------------------------------------------------- - Fix --------------------------------------------------------------------------------- - -* generic - * Update execute() service log message to include device alias - * Update unittests to handle authentication exceptions - * Update unittests for token learning - -* iosxr - * Update more prompt handling to support (END) prompt - - --------------------------------------------------------------------------------- - New --------------------------------------------------------------------------------- - -* iosxr - * New `monitor` service for IOS-XR with support for "monitor interface" command. - - diff --git a/docs/changelog_plugins/index.rst b/docs/changelog_plugins/index.rst index 85191282..9023e664 100644 --- a/docs/changelog_plugins/index.rst +++ b/docs/changelog_plugins/index.rst @@ -4,7 +4,7 @@ Plugins Changelog .. toctree:: :maxdepth: 2 - 2024/september + 2024/September 2024/august 2024/july 2024/june diff --git a/src/unicon/plugins/__init__.py b/src/unicon/plugins/__init__.py index 487371e7..3127f04c 100644 --- a/src/unicon/plugins/__init__.py +++ b/src/unicon/plugins/__init__.py @@ -1,4 +1,4 @@ -__version__ = '24.8' +__version__ = '24.9' supported_chassis = [ 'single_rp', diff --git a/src/unicon/plugins/apic/patterns.py b/src/unicon/plugins/apic/patterns.py index 7f56d46e..8d4949fd 100644 --- a/src/unicon/plugins/apic/patterns.py +++ b/src/unicon/plugins/apic/patterns.py @@ -6,9 +6,9 @@ class ApicPatterns(GenericPatterns): def __init__(self): super().__init__() - self.enable_prompt = r'^(.*?)(%N)#' - self.config_prompt = r'^(.*?)(%N)\(config.*\)#' - self.shell_prompt = r'^(.*?)(\[[-\.\w]+@(%N)\s+.*?\]#)\s*(\x1b\S+)?$' + self.enable_prompt = r'^(.*?)((\x1b\S+)?\x00)*(%N)#\s*(\x1b\S+)?$' + self.config_prompt = r'^(.*?)((\x1b\S+)?\x00)*(%N)\(config.*\)#\s*(\x1b\S+)?$' + self.shell_prompt = r'^(.*?)((\x1b\S+)?\x00)*\[[-\.\w]+@((%N)\s+.*?\]#)\s*(\x1b\S+)?$' class ApicSetupPatterns(object): diff --git a/src/unicon/plugins/apic/service_implementation.py b/src/unicon/plugins/apic/service_implementation.py index 0527d781..f5af6aef 100644 --- a/src/unicon/plugins/apic/service_implementation.py +++ b/src/unicon/plugins/apic/service_implementation.py @@ -51,7 +51,9 @@ def post_service(self, *args, clean_output=True, **kwargs): output = self.result output = utils.remove_ansi_escape_codes(output) output = re.sub('.\x08', '', output) - output = re.sub(r'%\s+\r ', '', output) + output = re.sub(r'\x00+', '', output) + output = re.sub(r'%(\s+\r )?', '', output) + output = re.sub(r'[\r\n]+', '', output) self.result = output diff --git a/src/unicon/plugins/generic/service_implementation.py b/src/unicon/plugins/generic/service_implementation.py index e2431e0d..83f890f0 100644 --- a/src/unicon/plugins/generic/service_implementation.py +++ b/src/unicon/plugins/generic/service_implementation.py @@ -28,7 +28,7 @@ from unicon.bases.routers.services import BaseService from unicon.core.errors import SubCommandFailure, StateMachineError, \ - CopyBadNetworkError, TimeoutError + CopyBadNetworkError, TimeoutError, UniconBackendDecodeError from unicon.eal.dialogs import Dialog from unicon.eal.dialogs import Statement from unicon.plugins.generic.statements import ( @@ -509,7 +509,7 @@ def call_service(self, target=None, command='', *args, **kwargs): try: sm.go_to(self.start_state, spawn, - context=handle.context, + context=handle.context, timeout=timeout) except Exception as err: raise SubCommandFailure("Failed to Bring device to Enable State", @@ -736,7 +736,7 @@ def call_service(self, command=[], # noqa: C901 self.result = dialog_match.match_output self.result = self.get_service_result() sm.detect_state(con.spawn, con.context) - except StateMachineError: + except (StateMachineError, UniconBackendDecodeError): raise except Exception as err: raise SubCommandFailure("Command execution failed", err) from err @@ -2618,6 +2618,20 @@ def __exit__(self, exc_type, exc_value, exc_tb): # do not suppress return False + def parse(self, *args, **kwargs): + abstract_args = kwargs.setdefault('abstract', {}) + device = getattr(self.conn, 'device', None) + if device: + abstract_args.update(dict( + os=[device.os, 'linux'], + platform=device.platform, + model=device.model, + pid=device.pid, + )) + return self.conn.device.parse(*args, **kwargs) + else: + self.conn.log.warning('No device object, parse method unavailable') + def __getattr__(self, attr): if attr in ('execute', 'sendline', 'send', 'expect'): return getattr(self.conn, attr) diff --git a/src/unicon/plugins/iosxe/patterns.py b/src/unicon/plugins/iosxe/patterns.py index ea092c24..c594a812 100644 --- a/src/unicon/plugins/iosxe/patterns.py +++ b/src/unicon/plugins/iosxe/patterns.py @@ -29,7 +29,7 @@ def __init__(self): self.maintenance_mode_prompt = \ r'^(.*?)(WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(standby\))?(-stby)?(-standby)?(\(boot\))?\(maint-mode\)#[\s\x07]*$' self.press_enter = ReloadPatterns().press_enter - self.config_prompt = r'^(.*)\((?!.*pki-hexmode).*(con|cfg|ipsec-profile|ca-trustpoint|cs-server|ca-profile|gkm-local-server|cloud|host-list|config-gkm-group|gkm-sa-ipsec|gdoi-coop-ks-config|wsma|enforce-rule)\S*\)#\s?$' + self.config_prompt = r'^(.*)\((?!.*pki-hexmode).*(con|cfg|ipsec-profile|ca-trustpoint|ca-certificate-map|cs-server|ca-profile|gkm-local-server|cloud|host-list|config-gkm-group|gkm-sa-ipsec|gdoi-coop-ks-config|wsma|enforce-rule)\S*\)#\s?$' self.config_pki_prompt = r'^(.*)\(config-pki-hexmode\)#\s?$' diff --git a/src/unicon/plugins/tests/mock_data/apic/apic_mock_data.yaml b/src/unicon/plugins/tests/mock_data/apic/apic_mock_data.yaml index d74b85df..c8177a99 100644 --- a/src/unicon/plugins/tests/mock_data/apic/apic_mock_data.yaml +++ b/src/unicon/plugins/tests/mock_data/apic/apic_mock_data.yaml @@ -9,6 +9,12 @@ apic_connect: apic_exec: prompt: APC# commands: &exec_commands + "show firmware upgrade status": | + \r\r\n ID Pod ID Name Serial Number IP Address Role State LastUpdMsgId\r\n + ------------------------------------------------------------------------------------- + \r\n 101 1 reg2_leaf1 FDO241909ZG 10.0.120.64/32 leaf active 0\r\n 102 1 reg2_leaf2 + FDO20510HAS 10.0.120.66/32 leaf active 0\r\n 201 1 reg2_spine1 FDO26450SH4 10.0.120.65/32 spine + active 0\r\n\r\nTotal 3 nodes\r\n\r\n% "terminal length 0": "" "terminal width 0": "" "show version": |2 @@ -39,6 +45,10 @@ apic_hostname_with_escape_codes: prompt: "%1B[0m%1B[27m%1B[24m%1B[JAPC-0001-2001# " commands: *exec_commands +apic_hostname_with_escape_codes2: + prompt: "\x1b[0m\x00\x00\x1b[m\x00\x00\x1b[m\x00\x00\x1b[J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00apic1# \x1b[K\x00\x00\x00\x1b[?1h\x1b=\x1b[?2004h" + commands: *exec_commands + apic_restart_confirm: prompt: "This command will restart this device, Proceed? [y/N] " diff --git a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data.yaml b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data.yaml index 4e76e8f5..bb2a467b 100644 --- a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data.yaml +++ b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data.yaml @@ -567,6 +567,10 @@ config_pki_hexmode: general_bash: prompt: "[%N_RP_0:/]$" commands: + "ls -l": | + total 55000 + lrwxrwxrwx. 1 root root 7 Sep 10 2024 bin -> usr/bin + drwxr-xr-x. 2 root root 60 Jan 9 20:41 boot "df /bootflash/": | Filesystem 1K-blocks Used Available Use% Mounted on /dev/sda1 5974888 3569476 2101900 63% /bootflash @@ -581,6 +585,14 @@ general_act_reply: "y": new_state: general_bash + +general_config_certificate_map: + prompt: "%N(ca-certificate-map)#" + commands: + "end": + new_state: general_enable + + general_config_crypto_trustpoint: prompt: "%N(ca-trustpoint)#" commands: diff --git a/src/unicon/plugins/tests/test_plugin_apic.py b/src/unicon/plugins/tests/test_plugin_apic.py index 6ef1c9bc..ac1f1fde 100644 --- a/src/unicon/plugins/tests/test_plugin_apic.py +++ b/src/unicon/plugins/tests/test_plugin_apic.py @@ -18,13 +18,14 @@ from unicon.mock.mock_device import MockDeviceSSHWrapper +APIC_MOCK_DEVICE_CLI = 'mock_device_cli --os apic --state apic_connect' @patch.object(unicon.settings.Settings, 'POST_DISCONNECT_WAIT_SEC', 0) @patch.object(unicon.settings.Settings, 'GRACEFUL_DISCONNECT_WAIT_SEC', 0.2) class TestAciApicPlugin(unittest.TestCase): def test_login_connect(self): c = Connection(hostname='APC', - start=['mock_device_cli --os apic --state apic_connect'], + start=[APIC_MOCK_DEVICE_CLI], os='apic', username='cisco', tacacs_password='cisco') @@ -33,7 +34,7 @@ def test_login_connect(self): def test_login_connect_credentials(self): c = Connection(hostname='APC', - start=['mock_device_cli --os apic --state apic_connect'], + start=[APIC_MOCK_DEVICE_CLI], os='apic', credentials={'default': { 'username': 'admin', @@ -52,9 +53,22 @@ def test_connect_escape_codes_learn_hostname(self): c.connect() c.disconnect() + def test_connect_escape_codes_learn_hostname2(self): + c = Connection(hostname='APC', + start=['mock_device_cli --os apic --state apic_hostname_with_escape_codes2'], + os='apic', + username='cisco', + tacacs_password='cisco', + debug=True, + learn_hostname=True) + try: + c.connect() + finally: + c.disconnect() + def test_reload(self): c = Connection(hostname='APC', - start=['mock_device_cli --os apic --state apic_connect'], + start=[APIC_MOCK_DEVICE_CLI], os='apic', username='admin', tacacs_password='cisco123') @@ -66,7 +80,7 @@ def test_reload(self): def test_reload_credentails(self): c = Connection(hostname='APC', - start=['mock_device_cli --os apic --state apic_connect'], + start=[APIC_MOCK_DEVICE_CLI], os='apic', credentials={'default': { 'username': 'admin', @@ -79,7 +93,7 @@ def test_reload_credentails(self): def test_config_prompt(self): c = Connection(hostname='APC', - start=['mock_device_cli --os apic --state apic_connect'], + start=[APIC_MOCK_DEVICE_CLI], os='apic', credentials={'default': { 'username': 'admin', @@ -91,7 +105,7 @@ def test_config_prompt(self): def test_execute_error_pattern(self): c = Connection(hostname='APC', - start=['mock_device_cli --os apic --state apic_connect'], + start=[APIC_MOCK_DEVICE_CLI], os='apic', username='cisco', tacacs_password='cisco') @@ -112,6 +126,15 @@ def test_connect_shell(self): self.assertEqual(out, '/root') c.disconnect() + def test_execute_output(self): + c = Connection(hostname='APC', + start=[APIC_MOCK_DEVICE_CLI], + os='apic', + username='cisco', + tacacs_password='cisco') + c.connect() + c.execute("show firmware upgrade status") + c.disconnect() @patch.object(unicon.settings.Settings, 'POST_DISCONNECT_WAIT_SEC', 0) @patch.object(unicon.settings.Settings, 'GRACEFUL_DISCONNECT_WAIT_SEC', 0.2) diff --git a/src/unicon/plugins/tests/test_plugin_iosxe.py b/src/unicon/plugins/tests/test_plugin_iosxe.py index 3adc490c..56643635 100644 --- a/src/unicon/plugins/tests/test_plugin_iosxe.py +++ b/src/unicon/plugins/tests/test_plugin_iosxe.py @@ -185,6 +185,18 @@ def test_general_config_ca_profile(self): finally: c.disconnect() + def test_general_config_ca_cert_map(self): + c = Connection(hostname='Router', + start=['mock_device_cli --os iosxe --state general_config_certificate_map --hostname Router'], + os='iosxe', + credentials=dict(default=dict(username='cisco', password='cisco'))) + + try: + c.connect() + self.assertEqual(c.state_machine.current_state, 'enable') + finally: + c.disconnect() + def test_gkm_local_server(self): c = Connection(hostname='Router', start=['mock_device_cli --os iosxe --state general_login --hostname Router'], @@ -557,6 +569,51 @@ def test_bash_disable_selinux_invalid_cmds(self): self.assertIn('Router#', c.spawn.match.match_output) c.disconnect() + def test_bash_parse(self): + testbed = ''' + devices: + R1: + os: iosxe + connections: + defaults: + class: 'unicon.Unicon' + cli: + command: mock_device_cli --os iosxe --state general_enable --hostname R1 + ''' + tb = loader.load(testbed) + dev = tb.devices.R1 + try: + dev.connect(mit=True, log_buffer=True) + with dev.bash_console() as console: + output = console.parse('ls -l') + expected_output = { + 'files': {'bin': {'day': 10, + 'filename': 'bin', + 'group': 'root', + 'linked_filename': 'usr/bin', + 'links': 1, + 'mode': 'lrwxrwxrwx.', + 'month': 'Sep', + 'size': 7, + 'user': 'root', + 'year': 2024}, + 'boot': {'day': 9, + 'filename': 'boot', + 'group': 'root', + 'hour': 20, + 'links': 2, + 'minute': 41, + 'mode': 'drwxr-xr-x.', + 'month': 'Jan', + 'size': 60, + 'user': 'root'}}, + 'total': 55000 + } + self.assertEqual(output, expected_output) + finally: + dev.disconnect() + + class TestIosXESDWANConfigure(unittest.TestCase): def test_config_transaction(self):