2828
2929from unicon .bases .routers .services import BaseService
3030from unicon .core .errors import SubCommandFailure , StateMachineError , \
31- CopyBadNetworkError , TimeoutError , UniconBackendDecodeError
31+ CopyBadNetworkError , TimeoutError , UniconBackendDecodeError , \
32+ UniconAuthenticationError , CredentialsExhaustedError
3233from unicon .eal .dialogs import Dialog
3334from unicon .eal .dialogs import Statement
3435from unicon .plugins .generic .statements import (
4748
4849from unicon .plugins .generic .utils import GenericUtils
4950from .service_statements import execution_statement_list , configure_statement_list
51+ from .statements import disable_enable_transition_statements
5052from unicon .plugins .generic .statemachine import config_transition
5153
5254utils = GenericUtils ()
@@ -480,6 +482,7 @@ def __init__(self, connection, context, **kwargs):
480482 super ().__init__ (connection , context , ** kwargs )
481483 self .start_state = 'enable'
482484 self .end_state = 'enable'
485+ self .dialog = Dialog (disable_enable_transition_statements )
483486 self .__dict__ .update (kwargs )
484487
485488 def pre_service (self , * args , ** kwargs ):
@@ -491,7 +494,7 @@ def call_service(self, target=None, command='', *args, **kwargs):
491494 handle = self .get_handle (target )
492495 spawn = self .get_spawn (target )
493496 sm = self .get_sm (target )
494- timeout = kwargs .get ('timeout' , None )
497+ timeout = kwargs .get ('timeout' , None ) or handle . settings . ENABLE_TIMEOUT
495498
496499 # If the device is in rommon, enable() will use the
497500 # image_to_boot info to boot the image specified
@@ -511,9 +514,13 @@ def call_service(self, target=None, command='', *args, **kwargs):
511514 spawn ,
512515 context = handle .context ,
513516 timeout = timeout )
517+ except (UniconAuthenticationError , CredentialsExhaustedError ):
518+ # Don't wrap auth errors - re-raise them directly
519+ raise
514520 except Exception as err :
515521 raise SubCommandFailure ("Failed to Bring device to Enable State" ,
516522 err ) from err
523+
517524 self .result = True
518525
519526
@@ -604,6 +611,7 @@ def __init__(self, connection, context, **kwargs):
604611 self .matched_retry_sleep = connection .settings .EXECUTE_MATCHED_RETRY_SLEEP
605612 self .state_change_matched_retries = connection .settings .EXECUTE_STATE_CHANGE_MATCH_RETRIES
606613 self .state_change_matched_retry_sleep = connection .settings .EXECUTE_STATE_CHANGE_MATCH_RETRY_SLEEP
614+ self .detect_state = True
607615
608616 def log_service_call (self ):
609617 pass
@@ -620,12 +628,15 @@ def call_service(self, command=[], # noqa: C901
620628 allow_state_change = None ,
621629 matched_retries = None ,
622630 matched_retry_sleep = None ,
631+ detect_state = None ,
623632 * args , ** kwargs ):
624633 con = self .connection
625634 sm = self .get_sm ()
626635 if allow_state_change is None :
627636 allow_state_change = con .settings .EXEC_ALLOW_STATE_CHANGE
628637
638+ self .detect_state = detect_state if detect_state is not None else self .detect_state
639+
629640 timeout = timeout or self .timeout
630641
631642 if error_pattern is None :
@@ -682,24 +693,25 @@ def call_service(self, command=[], # noqa: C901
682693 if custom_auth_stmt :
683694 dialog += Dialog (custom_auth_stmt )
684695
685- # Add all known states to detect state changes.
686- for state in sm .states :
687- # The current state is already added by the service_dialog method
688- if state .name != sm .current_state :
689- if allow_state_change :
690- dialog .append (Statement (
691- pattern = state .pattern ,
692- matched_retries = self .state_change_matched_retries ,
693- matched_retry_sleep = self .state_change_matched_retry_sleep
694- ))
695- else :
696- dialog .append (Statement (
697- pattern = state .pattern ,
698- action = invalid_state_change_action ,
699- args = {'err_state' : state , 'sm' : sm },
700- matched_retries = self .state_change_matched_retries ,
701- matched_retry_sleep = self .state_change_matched_retry_sleep
702- ))
696+ if self .detect_state :
697+ # Add all known states to detect state changes.
698+ for state in sm .states :
699+ # The current state is already added by the service_dialog method
700+ if state .name != sm .current_state :
701+ if allow_state_change :
702+ dialog .append (Statement (
703+ pattern = state .pattern ,
704+ matched_retries = self .state_change_matched_retries ,
705+ matched_retry_sleep = self .state_change_matched_retry_sleep
706+ ))
707+ else :
708+ dialog .append (Statement (
709+ pattern = state .pattern ,
710+ action = invalid_state_change_action ,
711+ args = {'err_state' : state , 'sm' : sm },
712+ matched_retries = self .state_change_matched_retries ,
713+ matched_retry_sleep = self .state_change_matched_retry_sleep
714+ ))
703715
704716 # store the last used dialog, used by unittest
705717 self ._last_dialog = dialog
@@ -926,7 +938,6 @@ def config_state_change(spawn, from_state, sm):
926938 if command :
927939 flat_cmd = self .utils .flatten_splitlines_command (command )
928940 dialog = self .dialog + self .service_dialog (handle = handle , service_dialog = reply )
929- sp = handle .spawn
930941 # Add all known states to detect state changes.
931942 for state in sm .states :
932943 # The current state is already added by the service_dialog method
@@ -945,38 +956,52 @@ def config_state_change(spawn, from_state, sm):
945956 matched_retries = self .state_change_matched_retries ,
946957 matched_retry_sleep = self .state_change_matched_retry_sleep
947958 ))
959+
960+ banner_lines , command_lines , banner_delim = self .get_banner_lines (flat_cmd )
961+
962+ # Populate context for banner_text_handler only if banner was detected
963+ if banner_lines :
964+ self .connection .log .info ('Banner detected, configuring banners without state detection' )
965+
966+ # Send banner lines
967+ for line in banner_lines :
968+ handle .spawn .sendline (line )
969+ time .sleep (0.1 )
970+ handle .spawn .read_update_buffer ()
971+
972+
948973 if bulk :
949974 indicator = handle .settings .BULK_CONFIG_END_INDICATOR
950- cmd_lst = list (chain (flat_cmd , [indicator ]))
975+ cmd_lst = list (chain (command_lines , [indicator ]))
951976 if bulk_chunk_lines == 0 :
952977 chunks = [cmd_lst ]
953978 else :
954979 chunks = [cmd_lst [i :i + bulk_chunk_lines ]
955980 for i in range (0 , len (cmd_lst ), bulk_chunk_lines )]
956981 for idx , chunk in enumerate (chunks , 1 ):
957982 chunk_cmd = '\n ' .join (chunk )
958- sp .sendline (chunk_cmd )
983+ handle . spawn .sendline (chunk_cmd )
959984 if idx != len (chunks ):
960985 sleep (bulk_chunk_sleep )
961- sp .read_update_buffer ()
986+ handle . spawn .read_update_buffer ()
962987 else :
963988 try :
964- sp .expect ([indicator ], timeout = timeout ,
989+ handle . spawn .expect ([indicator ], timeout = timeout ,
965990 trim_buffer = False )
966- self .result , _ , sp .buffer = \
967- sp .buffer .rpartition (indicator )
991+ self .result , _ , handle . spawn .buffer = \
992+ handle . spawn .buffer .rpartition (indicator )
968993 except Exception as err :
969994 raise SubCommandFailure ('Configuration failed' ,
970995 err ) from err
971996 self .process_dialog_on_handle (handle , dialog , timeout )
972997 if self .commit_cmd :
973- sp .sendline (self .commit_cmd )
998+ handle . spawn .sendline (self .commit_cmd )
974999 self .process_dialog_on_handle (handle , dialog , timeout )
9751000 else :
976- cmds = chain (flat_cmd , [self .commit_cmd ]) \
977- if self .commit_cmd else flat_cmd
1001+ cmds = chain (command_lines , [self .commit_cmd ]) \
1002+ if self .commit_cmd else command_lines
9781003 for cmd in cmds :
979- sp .sendline (cmd )
1004+ handle . spawn .sendline (cmd )
9801005 self .update_hostname_if_needed ([cmd ])
9811006 self .process_dialog_on_handle (handle , dialog , timeout )
9821007 # To handle the session
@@ -986,7 +1011,7 @@ def config_state_change(spawn, from_state, sm):
9861011 sleep (self .connection .settings .CONFIG_LOCK_RETRY_SLEEP )
9871012 config_transition (handle .state_machine , handle .spawn , handle .context )
9881013 handle .context ['config_session_locked' ] = False
989- sp .sendline (cmd )
1014+ handle . spawn .sendline (cmd )
9901015 self .process_dialog_on_handle (handle , dialog , timeout )
9911016
9921017 # store config_result so it can be returned to the user later
@@ -1005,6 +1030,37 @@ def config_state_change(spawn, from_state, sm):
10051030 # return the config_result to the user via self.result
10061031 self .result = config_result
10071032
1033+
1034+ def get_banner_lines (self , config_lines ):
1035+ """ Process lines related to the banner command
1036+ Args:
1037+ config_lines (list): list of config lines
1038+ Returns:
1039+ tuple: (banner_lines, command_lines, banner_delim)
1040+ """
1041+ banner_lines = []
1042+ command_lines = []
1043+ banner_delim = None
1044+
1045+ for line in config_lines :
1046+
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 )
1051+ continue
1052+
1053+ if banner_delim :
1054+ banner_lines .append (line )
1055+ # End of banner when delimiter repeats as a full line
1056+ if line .strip () == banner_delim :
1057+ banner_delim = None
1058+ continue
1059+
1060+ command_lines .append (line )
1061+
1062+ return banner_lines , command_lines , banner_delim
1063+
10081064 def process_dialog_on_handle (self , handle , dialog , timeout ):
10091065 try :
10101066 cmd_result = dialog .process (
0 commit comments