Skip to content

Commit c6fe55f

Browse files
author
Taarini Sarath Chander
committed
Releasing v26.3
1 parent 908f1ab commit c6fe55f

35 files changed

Lines changed: 825 additions & 301 deletions

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ BUILD_CMD = $(PYTHON) setup.py bdist_wheel --dist-dir=$(DIST_DIR)
99
PYPIREPO = pypitest
1010

1111

12-
DEPENDENCIES = robotframework pyyaml dill coverage Sphinx \
12+
DEPENDENCIES = robotframework pyyaml dill coverage Sphinx==7.4.7 \
1313
sphinxcontrib-napoleon sphinxcontrib-mockautodoc \
1414
sphinx-rtd-theme asyncssh PrettyTable "cryptography>=43.0"
1515

docs/changelog/2026/march.rst

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
March 2026
2+
==========
3+
4+
March 31 - Unicon v26.3
5+
------------------------
6+
7+
8+
9+
.. csv-table:: Module Versions
10+
:header: "Modules", "Versions"
11+
12+
``unicon.plugins``, v26.3
13+
``unicon``, v26.3
14+
15+
16+
17+
18+
Changelogs
19+
^^^^^^^^^^
20+
--------------------------------------------------------------------------------
21+
Fix
22+
--------------------------------------------------------------------------------
23+
24+
* bases/router/connection_provider
25+
* Added logic to raise a traceback when when HA sync does not complete within POST_BOOT_TIMEOUT
26+
27+
* removed unused `pyats` and `genie` imports
28+
29+
30+
--------------------------------------------------------------------------------
31+
New
32+
--------------------------------------------------------------------------------
33+
34+
* iosxe/ie9k
35+
* Added plugin settings for IE9k platform.
36+
37+
* iosxe/ie3k
38+
* Added plugin settings for IE3k platform.
39+
40+
41+
--------------------------------------------------------------------------------
42+
Fix
43+
--------------------------------------------------------------------------------
44+
45+
* iosxe
46+
* Fixed boot image to support multiple filesystems.
47+
* Fixed encryption selection criteria on boot.
48+
49+
* iosxe/iec3400
50+
* Removed this platform as it must be ie3k.
51+
* Related state machine and test cases were not needed hence weren't moved to ie3k.
52+
53+
* pid_tokens.csv
54+
* Modified PID tokens
55+
* Added IE9k family PID token mappings.
56+
* Added ESS3300/ESS9300 family PID token mappings.
57+
58+
* pid_tokens
59+
* Added PID tokens for IE 3100, 3500 series
60+
61+
* iosxe/settings
62+
* Increased POST_BOOT_TIMEOUT to allow for longer HA sync times.
63+
64+
* iosxe/patterns
65+
* Modified want_continue pattern to match the prompt "Continue? [no]"
66+
67+

docs/changelog/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Changelog
44
.. toctree::
55
:maxdepth: 2
66

7+
2026/march
78
2026/february
89
2026/january
910
2025/december
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
March 2026
2+
==========
3+
4+
March 31 - Unicon.Plugins v26.3
5+
------------------------
6+
7+
8+
9+
.. csv-table:: Module Versions
10+
:header: "Modules", "Versions"
11+
12+
``unicon.plugins``, v26.3
13+
``unicon``, v26.3
14+
15+
16+
17+
18+
Changelogs
19+
^^^^^^^^^^
20+
--------------------------------------------------------------------------------
21+
Fix
22+
--------------------------------------------------------------------------------
23+
24+
* generic
25+
* Configure service
26+
* Refactored banner handling logic to improve maintainability.
27+
* Updated banner processing to send lines sequentially with appropriate delays for device processing.
28+
* HAReloadService
29+
* Fixed command fallback check and added guard to skip sendline when command is empty.
30+
* SwitchoverService
31+
* Fixed command fallback check and added guard to skip sendline when command is empty.
32+
33+
* iosxe
34+
* Configure service
35+
* Updated ACM configlet implementation to use connection context for acm_configlet parameter.
36+
* Ensures proper persistence of acm_configlet during state transitions.
37+
* Fixed multiline banner configuration to support variable delimiters
38+
* HASwitchover
39+
* Changed default command parameter from [] to None.
40+
41+
* iosxe/cat9k
42+
* stackwise_virtual
43+
* Updated the logic to detect current state before during state change
44+
* Updated the logic of designate handles to correctly identify active and standby state after svl configuration.
45+
* 9500x/stackwise_virtual
46+
* Updated the logic to detect current state before during state change
47+
48+
* iosxe/cat4k
49+
* Reload
50+
* Fixed reload_command fallback check and added guard to skip sendline when empty.
51+
52+
* iosxe/cat8k
53+
* SwitchoverService
54+
* Fixed command fallback check and added guard to skip sendline when empty.
55+
56+
* iosxe/stack
57+
* StackSwitchover
58+
* Fixed command fallback check and added guard to skip sendline when empty.
59+
* StackReload
60+
* Fixed reload_command fallback check and added guard to skip sendline when empty.
61+
62+
* iosxe/quad
63+
* QuadSwitchover
64+
* Fixed command fallback check and added guard to skip sendline when empty.
65+
* QuadReload
66+
* Fixed reload_command fallback check and added guard to skip sendline when empty.
67+
68+
* iosxe/cat9k/c9350/stack
69+
* C9350StackReload
70+
* Fixed reload_command fallback check and added guard to skip sendline when empty.
71+
72+
* iosxe/cat9k/c9500x/stackwise_virtual
73+
* SVLStackReload
74+
* Fixed reload_command fallback check, added guard for empty command and improved post-reload recovery and reconnection handling.
75+
* SVLStackSwitchover
76+
* Fixed command fallback check and added guard to skip sendline when empty.
77+
78+

docs/changelog_plugins/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Plugins Changelog
44
.. toctree::
55
:maxdepth: 2
66

7+
2026/march
78
2026/february
89
2026/january
910
2025/december

src/unicon/plugins/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "26.2"
1+
__version__ = "26.3"
22

33
supported_chassis = [
44
'single_rp',

src/unicon/plugins/generic/service_implementation.py

Lines changed: 56 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -934,9 +934,11 @@ def config_state_change(spawn, from_state, sm):
934934
else:
935935
sm.update_cur_state(from_state)
936936

937+
# Flatten multi-line input into one-command-per-line list.
938+
flat_cmd_list = list(self.utils.flatten_splitlines_command(command) if command else [])
939+
937940
self.result = ''
938-
if command:
939-
flat_cmd = self.utils.flatten_splitlines_command(command)
941+
if flat_cmd_list:
940942
dialog = self.dialog + self.service_dialog(handle=handle, service_dialog=reply)
941943
# Add all known states to detect state changes.
942944
for state in sm.states:
@@ -957,22 +959,35 @@ def config_state_change(spawn, from_state, sm):
957959
matched_retry_sleep=self.state_change_matched_retry_sleep
958960
))
959961

960-
banner_lines, command_lines, banner_delim = self.get_banner_lines(flat_cmd)
962+
# Use flattened command list
963+
pre_lines, banner_lines, post_lines, banner_delim = self.get_banner_lines(flat_cmd_list)
961964

962965
# Populate context for banner_text_handler only if banner was detected
963966
if banner_lines:
964967
self.connection.log.info('Banner detected, configuring banners without state detection')
965968

969+
for cmd in pre_lines:
970+
handle.spawn.sendline(cmd)
971+
self.update_hostname_if_needed([cmd])
972+
self.process_dialog_on_handle(handle, dialog, timeout)
973+
966974
# Send banner lines
967975
for line in banner_lines:
968976
handle.spawn.sendline(line)
969977
time.sleep(0.1)
970978
handle.spawn.read_update_buffer()
971979

980+
self.process_dialog_on_handle(handle, dialog, timeout)
972981

973-
if bulk:
982+
post_cmds = chain(post_lines, [self.commit_cmd]) if self.commit_cmd else post_lines
983+
for cmd in post_cmds:
984+
handle.spawn.sendline(cmd)
985+
self.update_hostname_if_needed([cmd])
986+
self.process_dialog_on_handle(handle, dialog, timeout)
987+
988+
elif bulk:
974989
indicator = handle.settings.BULK_CONFIG_END_INDICATOR
975-
cmd_lst = list(chain(command_lines, [indicator]))
990+
cmd_lst = list(chain(flat_cmd_list, [indicator]))
976991
if bulk_chunk_lines == 0:
977992
chunks = [cmd_lst]
978993
else:
@@ -998,8 +1013,8 @@ def config_state_change(spawn, from_state, sm):
9981013
handle.spawn.sendline(self.commit_cmd)
9991014
self.process_dialog_on_handle(handle, dialog, timeout)
10001015
else:
1001-
cmds = chain(command_lines, [self.commit_cmd]) \
1002-
if self.commit_cmd else command_lines
1016+
cmds = chain(flat_cmd_list, [self.commit_cmd]) \
1017+
if self.commit_cmd else flat_cmd_list
10031018
for cmd in cmds:
10041019
handle.spawn.sendline(cmd)
10051020
self.update_hostname_if_needed([cmd])
@@ -1032,34 +1047,49 @@ def config_state_change(spawn, from_state, sm):
10321047

10331048

10341049
def get_banner_lines(self, config_lines):
1035-
""" Process lines related to the banner command
1050+
""" Process lines related to the banner command.
1051+
Handles detection and separation of banner configuration blocks from
1052+
regular configuration commands. Supports the first banner block only;
1053+
subsequent banners (if any) will be processed sequentially.
10361054
Args:
1037-
config_lines (list): list of config lines
1055+
config_lines (list): Configuration command lines
10381056
Returns:
1039-
tuple: (banner_lines, command_lines, banner_delim)
1057+
tuple: (pre_lines, banner_lines, post_lines, banner_delim)
1058+
- pre_lines: Commands before the banner block
1059+
- banner_lines: The banner initiation line and content
1060+
- post_lines: Commands after the banner block
1061+
- banner_delim: The delimiter character used for the banner
10401062
"""
1041-
banner_lines = []
1042-
command_lines = []
1063+
pre_lines, banner_lines, post_lines = [], [], []
10431064
banner_delim = None
1065+
in_banner = False
1066+
banner_seen = False
10441067

10451068
for line in config_lines:
10461069

1047-
match = re.match(r'^\s*banner\s+(login|motd|exec|incoming)\s+(\S)', line)
1048-
if match:
1049-
banner_lines.append(line)
1050-
banner_delim = match.group(2)
1070+
if not in_banner and not banner_seen:
1071+
match = re.match(r'^\s*banner\s+(login|motd|exec|incoming)\s+(\S+)', line)
1072+
if match:
1073+
banner_lines.append(line)
1074+
raw_delim = match.group(2)
1075+
# Use '^C' token when present, else first character (e.g. '%')
1076+
banner_delim = '^C' if raw_delim.startswith('^C') else raw_delim[0]
1077+
in_banner = True
1078+
banner_seen = True
1079+
continue
1080+
pre_lines.append(line)
10511081
continue
10521082

1053-
if banner_delim:
1083+
if in_banner:
10541084
banner_lines.append(line)
10551085
# End of banner when delimiter repeats as a full line
10561086
if line.strip() == banner_delim:
1057-
banner_delim = None
1087+
in_banner = False
10581088
continue
10591089

1060-
command_lines.append(line)
1090+
post_lines.append(line)
10611091

1062-
return banner_lines, command_lines, banner_delim
1092+
return pre_lines, banner_lines, post_lines, banner_delim
10631093

10641094
def process_dialog_on_handle(self, handle, dialog, timeout):
10651095
try:
@@ -2175,7 +2205,7 @@ def call_service(self, # noqa: C901
21752205
if command and reload_command:
21762206
raise SubCommandFailure(
21772207
"Please use either 'command' or 'reload_command' parameter")
2178-
command = command or reload_command or self.command
2208+
command = command if command is not None else (reload_command if reload_command is not None else self.command)
21792209

21802210
# TODO counter value must be moved to settings
21812211
counter = 0
@@ -2197,7 +2227,8 @@ def call_service(self, # noqa: C901
21972227
dialog += Dialog(custom_auth_stmt)
21982228

21992229
# Issue reload command
2200-
con.active.spawn.sendline(command)
2230+
if command:
2231+
con.active.spawn.sendline(command)
22012232
try:
22022233
reload_output = dialog.process(con.active.spawn,
22032234
context=context,
@@ -2373,7 +2404,7 @@ def call_service(self, command=None, # noqa: C901
23732404
category=DeprecationWarning)
23742405

23752406
timeout = timeout or self.timeout
2376-
command = command or self.command
2407+
command = command if command is not None else self.command
23772408
switchover_counter = con.settings.SWITCHOVER_COUNTER
23782409
con.log.debug("+++ Issuing switchover on %s with "
23792410
"switchover_command %s and timeout is %s +++"
@@ -2403,7 +2434,8 @@ def call_service(self, command=None, # noqa: C901
24032434
context = con.standby.context
24042435

24052436
# Issue switchover command
2406-
con.active.spawn.sendline(command)
2437+
if command:
2438+
con.active.spawn.sendline(command)
24072439
try:
24082440
dialog.process(con.active.spawn,
24092441
timeout=timeout,

src/unicon/plugins/generic/statements.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,7 @@ def enable_secret_handler(spawn, context, session):
339339
spawn.log.warning('Using enable secret from TEMP_ENABLE_SECRET setting')
340340
enable_secret = spawn.settings.TEMP_ENABLE_SECRET
341341
context['setup_selection'] = 0
342+
context['encryption_selection'] = 2
342343
spawn.sendline(enable_secret)
343344

344345

@@ -352,6 +353,16 @@ def setup_enter_selection(spawn, context):
352353
spawn.sendline('2')
353354

354355

356+
def setup_enter_encryption_selection(spawn, context):
357+
selection = context.get('encryption_selection', context.get('setup_selection'))
358+
if selection is not None:
359+
if str(selection) == '0':
360+
spawn.log.warning('Not saving setup configuration')
361+
spawn.sendline(f'{selection}')
362+
else:
363+
spawn.sendline('2')
364+
365+
355366
def ssh_tacacs_handler(spawn, context):
356367
result = False
357368
start_cmd = spawn.spawn_command
@@ -781,7 +792,7 @@ def __init__(self):
781792
continue_timer=False)
782793

783794
self.enter_your_encryption_selection_stmt = Statement(pattern=pat.enter_your_encryption_selection_2,
784-
action=setup_enter_selection,
795+
action=setup_enter_encryption_selection,
785796
args=None,
786797
loop_continue=True,
787798
continue_timer=True)

src/unicon/plugins/iosxe/cat4k/service_implementation.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def call_service(self, # noqa: C901
105105

106106
timeout = timeout or self.timeout
107107

108-
command = reload_command or self.command
108+
command = reload_command if reload_command is not None else self.command
109109

110110
fmt_str = "+++ reloading %s with reload_command %s and timeout is %s +++"
111111
con.log.info(fmt_str % (con.hostname, command, timeout))
@@ -125,7 +125,8 @@ def call_service(self, # noqa: C901
125125
dialog += Dialog(custom_auth_stmt)
126126

127127
# Issue reload command
128-
con.active.spawn.sendline(command)
128+
if command:
129+
con.active.spawn.sendline(command)
129130
try:
130131
dialog.process(con.active.spawn,
131132
context=context,

0 commit comments

Comments
 (0)