Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions docs/changelog/2025/april.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
April 2025
==========

April 29 - Unicon v25.4
------------------------



.. csv-table:: Module Versions
:header: "Modules", "Versions"

``unicon.plugins``, v25.4
``unicon``, v25.4




Changelogs
^^^^^^^^^^
--------------------------------------------------------------------------------
New
--------------------------------------------------------------------------------

* connection
* Added os learn version
* Added ability to set learn_tokens and overwrite_testbed_tokens from a config file or environment variable
* Environment Variables
* UNICON_LEARN_TOKENS
* UNICON_OVERWRITE_TESTBED_TOKENS
* UNICON_LEARN_AND_OVERWRITE_TOKENS
* Config File
* [unicon]
* learn_tokens
* overwrite_testbed_tokens
* learn_and_overwrite_tokens

* connection_provider
* Modified update_os_version
* Updated logic to execute 'show install summary' only on first connnection


--------------------------------------------------------------------------------
Fix
--------------------------------------------------------------------------------

* linux
* Modified linux connection provider
* Wait for connection_timeout/2 on initial connection for the device to respond with some output

* generic
* Add TRANSITION_WAIT setting to make transition wait time configurable


--------------------------------------------------------------------------------
Fix
--------------------------------------------------------------------------------

* linux
* Added support for prompt_line

* iosxe
* IosXEPatterns
* Updated the recovery-mode regex to match prompt

* iosxe_mock_data.yaml
* Added 'show install summary' output in mock yaml


--------------------------------------------------------------------------------
New
--------------------------------------------------------------------------------

* iosxe
* add test for learn os


1 change: 1 addition & 0 deletions docs/changelog/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Changelog
.. toctree::
:maxdepth: 2

2025/april
2025/march
2025/february
2025/january
Expand Down
45 changes: 45 additions & 0 deletions docs/changelog_plugins/2025/april.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
April 2025
==========

April 29 - Unicon.Plugins v25.4
------------------------



.. csv-table:: Module Versions
:header: "Modules", "Versions"

``unicon.plugins``, v25.4
``unicon``, v25.4




Changelogs
^^^^^^^^^^
--------------------------------------------------------------------------------
Fix
--------------------------------------------------------------------------------

* iosxr
* Update admin host pattern
* Update prompt commands to recover console

* generic
* Updated output variable by passing count argument, To get get rid of messages like 'DeprecationWarning 'count' is passed as positional argument'
* Update escape handler to support a list of prompt commands


--------------------------------------------------------------------------------
New
--------------------------------------------------------------------------------

* iosxe
* Cat9k
* Support for HA ROMMON
* Added support for no enable password being set. A UniconAuthenticationError will be raised if the enable password is not set and the user tries to enable the device.

* generic
* Return output of HAReloadService to match with generic ReloadService


1 change: 1 addition & 0 deletions docs/changelog_plugins/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Plugins Changelog
.. toctree::
:maxdepth: 2

2025/april
2025/march
2025/february
2025/january
Expand Down
27 changes: 27 additions & 0 deletions docs/user_guide/connection.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1544,6 +1544,19 @@ To make use of this feature, you can choose from the following actions:
arguments:
learn_tokens: True

4. Use a pyats.conf file

.. code-block:: ini

[unicon]
learn_tokens = True

5. Use an environment variable

.. code-block:: bash

export UNICON_LEARN_TOKENS=1

By default, token discovery will not overwrite tokens that you have already defined in your testbed file.
It will only assign discovered tokens to the device object if the token does not yet exist or if the value is generic. For example: `platform: generic`.

Expand Down Expand Up @@ -1588,3 +1601,17 @@ This flag can be set in the same way as `learn_tokens`:
learn_tokens: True
overwrite_testbed_tokens: True

4. Use a pyats.conf file

.. code-block:: ini

[unicon]
learn_tokens = True
overwrite_testbed_tokens = True

5. Use an environment variable

.. code-block:: bash

export UNICON_LEARN_TOKENS=1
export UNICON_OVERWRITE_TESTBED_TOKENS=1
2 changes: 1 addition & 1 deletion src/unicon/plugins/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = '25.3'
__version__ = '25.4'

supported_chassis = [
'single_rp',
Expand Down
2 changes: 2 additions & 0 deletions src/unicon/plugins/generic/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,5 @@ def __init__(self):
self.new_password = r'^(Enter new password|Confirm password):\s*$'

self.enter_your_encryption_selection_2 = r'^.*?Enter your encryption selection( \[2])?:\s*$'

self.no_password_set = r'^.*% (No password set|Error in authentication.).*'
27 changes: 23 additions & 4 deletions src/unicon/plugins/generic/service_implementation.py
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,7 @@ def call_service(self, command=[], # noqa: C901
output = self.extra_output_process(output)
output = output.replace(command, "", 1)
# only strip first newline and leave formatting intact
output = re.sub(r"^\r?\r\n", "", output, 1)
output = re.sub(r"^\r?\r\n", "", output, count=1)
output = output.rstrip()

if command in command_output:
Expand Down Expand Up @@ -1083,9 +1083,6 @@ def __init__(self, connection, context, **kwargs):
self.timeout = connection.settings.RELOAD_TIMEOUT
self.dialog = Dialog(reload_statement_list + default_statement_list)
self.log_buffer = io.StringIO()
lb = UniconStreamHandler(self.log_buffer)
lb.setFormatter(logging.Formatter(fmt=UNICON_LOG_FORMAT))
self.connection.log.addHandler(lb)
self.__dict__.update(kwargs)

def call_service(self,
Expand Down Expand Up @@ -1124,6 +1121,10 @@ def call_service(self,
raise ValueError('append_error_pattern should be a list')
self.error_pattern += append_error_pattern

lb = UniconStreamHandler(self.log_buffer)
lb.setFormatter(logging.Formatter(fmt=UNICON_LOG_FORMAT))
self.connection.log.addHandler(lb)

# Clear log buffer
self.log_buffer.seek(0)
self.log_buffer.truncate()
Expand Down Expand Up @@ -1235,6 +1236,8 @@ def call_service(self,
# clear buffer
self.log_buffer.truncate()

self.connection.log.removeHandler(lb)

if return_output:
self.result = ReloadResult(self.result, reload_output)

Expand Down Expand Up @@ -2053,6 +2056,7 @@ def __init__(self, connection, context, **kwargs):
self.timeout = connection.settings.HA_RELOAD_TIMEOUT
self.dialog = Dialog(ha_reload_statement_list + default_statement_list)
self.command = 'reload'
self.log_buffer = io.StringIO()
self.__dict__.update(kwargs)

def call_service(self, # noqa: C901
Expand Down Expand Up @@ -2085,6 +2089,14 @@ def call_service(self, # noqa: C901
raise ValueError('append_error_pattern should be a list')
self.error_pattern += append_error_pattern

lb = UniconStreamHandler(self.log_buffer)
lb.setFormatter(logging.Formatter(fmt=UNICON_LOG_FORMAT))
self.connection.log.addHandler(lb)

# Clear log buffer
self.log_buffer.seek(0)
self.log_buffer.truncate()

if reply:
if dialog:
con.log.warning("**** Both 'reply' and 'dialog' were provided "
Expand Down Expand Up @@ -2232,6 +2244,13 @@ def call_service(self, # noqa: C901
counter += 1

con.log.info("+++ Reload Completed Successfully +++")
self.log_buffer.seek(0)
reload_output = self.log_buffer.read()
# clear buffer
self.log_buffer.truncate()

self.connection.log.removeHandler(lb)

self.result = True
if return_output:
self.result = ReloadResult(self.result, reload_output)
Expand Down
3 changes: 3 additions & 0 deletions src/unicon/plugins/generic/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ def __init__(self):
# (wait time: 0.5, 1, 1.5, 2, 2.5, 3, 3.5 == total wait: 14.0s)
self.ESCAPE_CHAR_PROMPT_WAIT_RETRIES = 7

# commands to get a prompt, default to "enter"
self.ESCAPE_CHAR_PROMPT_COMMANDS = ['\r']

# syslog message handling timers
self.SYSLOG_WAIT = 1
# syslog wait time for reload service
Expand Down
27 changes: 25 additions & 2 deletions src/unicon/plugins/generic/statements.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,21 @@ def escape_char_callback(spawn):
# store current know buffer
known_buffer = len(spawn.buffer.strip())

# list of commands to iterate through
cmds = spawn.settings.ESCAPE_CHAR_PROMPT_COMMANDS
iter_cmds = iter(cmds)

for retry_number in range(spawn.settings.ESCAPE_CHAR_PROMPT_WAIT_RETRIES):
# hit enter
spawn.sendline()

# iterate through the commands
try:
cmd = next(iter_cmds)
except StopIteration:
iter_cmds = iter(cmds)
cmd = next(iter(iter_cmds))

# send command (typically "\r")
spawn.send(cmd)

# incremental wait logic
buffer_wait(spawn, spawn.settings.ESCAPE_CHAR_PROMPT_WAIT * (retry_number + 1))
Expand Down Expand Up @@ -453,6 +465,10 @@ def incorrect_login_handler(spawn, context, session):
raise UniconAuthenticationError(
'Login failure, either wrong username or password')

def no_password_handler(spawn, context, session):
""" handles no password prompt
"""
raise UniconAuthenticationError('No password set on this device')

def sudo_password_handler(spawn, context, session):
""" Password handler for sudo command
Expand Down Expand Up @@ -593,6 +609,12 @@ def __init__(self):
loop_continue=True,
continue_timer=False)

self.no_password_set_stmt = Statement(pattern=pat.no_password_set,
action=no_password_handler,
args=None,
loop_continue=True,
continue_timer=False)

self.disconnect_error_stmt = Statement(pattern=pat.disconnect_message,
action=connection_failure_handler,
args=None,
Expand Down Expand Up @@ -782,6 +804,7 @@ def __init__(self):

authentication_statement_list = [generic_statements.bad_password_stmt,
generic_statements.login_incorrect,
generic_statements.no_password_set_stmt,
generic_statements.login_stmt,
generic_statements.useraccess_stmt,
generic_statements.new_password_stmt,
Expand Down
27 changes: 27 additions & 0 deletions src/unicon/plugins/iosxe/cat9k/service_implementation.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,30 @@ def pre_service(self, *args, **kwargs):
else:
raise SubCommandFailure('Could not determine if break is enabled, cannot transition to rommon')
super().pre_service(*args, **kwargs)


class HARommon(Rommon):
""" Brings device to the Rommon prompt and executes commands specified
"""
def __init__(self, connection, context, **kwargs):
super().__init__(connection, context, **kwargs)

def pre_service(self, *args, **kwargs):
con = self.connection

# call pre_service to reload to rommon
super().pre_service(*args, **kwargs)

# check connection states
subcon1, subcon2 = list(con._subconnections.values())

# Check current state
for subcon in [subcon1, subcon2]:
subcon.sendline()
subcon.state_machine.go_to(
'any',
subcon.spawn,
context=subcon.context,
prompt_recovery=subcon.prompt_recovery,
timeout=subcon.connection_timeout,
)
4 changes: 2 additions & 2 deletions src/unicon/plugins/iosxe/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def __init__(self):
self.disable_prompt = \
r'^(.*?)(\(unlicensed\))?(WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(standby\))?(-stby)?(-standby)?(\(boot\))?(\(recovery-mode\))?>\s?$'
self.enable_prompt = \
r'^(.*?)(\(unlicensed\))?(WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(standby\))?(-stby)?(-standby)?(\(boot\))?(\(recovery-mode\))?#[\s\x07]*$'
r'^(.*?)(\(unlicensed\))?(WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(recovery-mode\))?(\(standby\))?(-stby)?(-standby)?(\(boot\))?#[\s\x07]*$'
self.maintenance_mode_prompt = \
r'^(.*?)(\(unlicensed\))?(WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(standby\))?(-stby)?(-standby)?(\(boot\))?\(maint-mode\)#[\s\x07]*$'
self.press_enter = ReloadPatterns().press_enter
Expand Down Expand Up @@ -57,7 +57,7 @@ def __init__(self):
self.default_prompts = r'^(.*?)(WLC|Router|RouterRP|Switch|ios|switch|.*)([0-9])?(\(standby\))?(\(boot\))?(>|#)'
self.telnet_prompt = r'^.*telnet>\s?'
self.please_reset = r'^(.*)Please reset'
self.grub_prompt = r'.*The highlighted entry will be (booted|executed) automatically'
self.grub_prompt = r'.*The highlighted entry will be (booted|executed) automatically in .*?(\x1b\S+)?\s+'

# The uniclean package expects these patterns to be here.
self.enable_prompt = IosXEPatterns().enable_prompt
Expand Down
1 change: 1 addition & 0 deletions src/unicon/plugins/iosxe/statemachine.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ def create(self):
disable_to_enable = Path(disable, enable, 'enable', Dialog([
statements.password_stmt,
statements.enable_password_stmt,
statements.no_password_set_stmt,
statements.bad_password_stmt,
statements.syslog_stripper_stmt
]))
Expand Down
3 changes: 2 additions & 1 deletion src/unicon/plugins/iosxr/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ def __init__(self):
self.admin_conf_prompt = r'^(.*?)(?:sysadmin-vm:0_(.*)\(config.*\)\s?#\s?|RP/\S+\(admin-config(\S+)?\)\s?#\s?)$'
self.admin_run_prompt = r'^(.*?)(?:\[sysadmin-vm:0_.*:([\s\S]+)?\]\s?\$\s?|[\r\n]+\s?#\s?)$'
# [host:0_RP0:~]$
self.admin_host_prompt = r'^(.*?)(?:\[host:0_.*:([\s\S]+)?\]\s?\$\s?)$'
# [ios:~]$
self.admin_host_prompt = r'^(.*?)(?:\[(host|ios):.*?\]\s?\$\s?)$'
self.unreachable_prompt = r'apples are green but oranges are red'
self.configuration_failed_message = r'^.*Please issue \'show configuration failed \[inheritance\].*[\r\n]*'
self.standby_prompt = r'^.*This \(D\)RP Node is not ready or active for login \/configuration.*'
Expand Down
Loading
Loading