def get_sub_commands(self, root_cmd): """ Gets a list of commands at the current mode. It sends root_cmd with ? and returns everything that is a command. This strips out things in <>'s, or other free-form fields the user has to enter. :param root_cmd: root of the command to get subcommands for :return: a list of the full paths to subcommands. eg, if root_cmd is "web ?", this returns: .. code-block:: python ['web autologout', 'web auto-refresh', ...] """ self._log.debug('Generating help for "%s"' % root_cmd) sub_commands = [] output, match_res = self._send_and_wait('%s ?' % root_cmd, self.CLI_ANY_PROMPT) # Split the output into a list of lines. The first one will be the # command we sent, the last two will be an escape code and the prompt, # So remove those. lines = output.splitlines() lines = lines[1:] self._log.debug("raw output: %s" % lines) for line in lines: command = line.split(' ')[0] if command == '%': # Remove the command we enter to be back at the empty prompt self._send_line_and_wait(DELETE_LINE, self.CLI_ANY_PROMPT) try: mode = self.current_cli_mode() except exceptions.UnknownCLIMode: mode = '<unrecognized>' raise exceptions.CLIError(root_cmd, output=output, mode=mode) # If this is a user-input field, skip it. Most are surrounded by # <>, but not all. If the command contains anything other than # letters or numbers, we assume it is a user field. if command.isalnum(): if root_cmd: sub_commands.append(root_cmd + ' ' + command) else: sub_commands.append(command) # Remove the command we enter, so we're back at the empty prompt self._send_line_and_wait(DELETE_LINE, self.CLI_ANY_PROMPT) return sub_commands
def enter_mode_normal(self, force=False): """ Puts the CLI into the 'normal' mode. In this mode you can run commands, but you cannot change the configuration. :param force: Will force enter 'normal' mode, discarding all changes that haven't been committed. :type force: Boolean :raises CLIError: if unable to go from "configure" mode to "normal" This happens if "commit" is not applied after config changes :raises UnknownCLIMode: if mode is not "normal" or "configure" """ self._log.info('Going to normal mode') mode = self.current_cli_mode() if mode == cli.CLIMode.NORMAL: self._log.debug('Already at normal, doing nothing') elif mode == cli.CLIMode.CONFIG: if force: self._log.debug('Entering normal mode, discarding all commits') self._send_line_and_wait( 'exit discard', self.CLI_NORMAL_PROMPT) else: self._log.debug('Entering normal mode') (output, match_res) = self._send_line_and_wait( 'exit', [self.CLI_ERROR_PROMPT, self.CLI_NORMAL_PROMPT]) if re.match(self.CLI_ERROR_PROMPT, match_res.string): raise exceptions.CLIError( command="exit", output=match_res.string, mode=mode) else: raise exceptions.UnknownCLIMode(mode=mode)
def exec_command(self, command, timeout=60, mode=cli.CLIMode.UNDEF, output_expected=None, error_expected=False, prompt=None): """Executes the given command. This method handles detecting simple boolean conditions such as the presence of output or errors. :param command: command to execute, newline appended automatically :param timeout: maximum time, in seconds, to wait for the command to finish. 0 to wait forever. :param mode: mode to enter before running the command. The default is :func:`default_mode`. To skip this step and execute directly in the cli's current mode, explicitly set this parameter to None. :param output_expected: If not None, indicates whether output is expected (True) or no output is expected (False). If the opposite occurs, raise UnexpectedOutput. Default is None. :type output_expected: bool or None :param error_expected: If true, cli error output (with a leading '%') is expected and will be returned as regular output instead of raising a CLIError. Default is False, and error_expected always overrides output_expected. :type error_expected: bool :param prompt: Prompt regex for matching unusual prompts. This should almost never be used as the ``mode`` parameter automatically handles all typical cases. This parameter is for unusual situations like the install config wizard. :return: output of the command, minus the command itself. :raises CmdlineTimeout: on timeout :raises CLIError: if the output matches the cli's error format, and error output was not expected. :raises UnexpectedOutput: if output occurs when no output was expected, or no output occurs when output was expected """ if not (output_expected is None or isinstance(output_expected, bool)): err_text = ("exec_command: output_expected requires a boolean " "value or None. {cmd} called with output_expected " "type:{type}, value:{val}") raise TypeError( err_text.format(cmd=command, type=type(output_expected), val=output_expected)) if mode is cli.CLIMode.UNDEF: mode = self.default_mode if mode is not None: self.enter_mode(mode) self._log.debug('Executing cmd "%s"' % command) if prompt is None: prompt = self._prompt try: (output, match_res) = self._send_line_and_wait(command, prompt, timeout=timeout) except exceptions.ConnectionError as e: self._log.info("Connection channel in unexpected state. Flushing " "and restarting. Error was {error}".format(error=e)) self._cleanup_helper() self.start() (output, match_res) = self._send_line_and_wait(command, prompt, timeout=timeout) # CLI adds on escape chars and such sometimes and the result is that # some part of the command that was entered shows up as an extra # initial line of output. Strip off that initial line. output = '\n'.join(output.splitlines()[1:]) if output and (re.match(self.CLI_ERROR_PROMPT, output)): if error_expected: # Skip output_expected processing entirely. return output else: try: mode = self.current_cli_mode() except exceptions.UnknownCLIMode: mode = '<unrecognized>' raise exceptions.CLIError(command, output=output, mode=mode) if ((output_expected is not None) and (bool(output) != bool(output_expected))): raise exceptions.UnexpectedOutput(command=command, output=output, expected_output=output_expected) return output
def exec_command(self, command, timeout=60, mode=cli.CLIMode.CONFIG, output_expected=None, error_expected=False, interface=None, prompt=None): """Executes the given command. This method handles detecting simple boolean conditions such as the presence of output or errors. :param command: command to execute, newline appended automatically :param timeout: maximum time, in seconds, to wait for the command to finish. 0 to wait forever. :param mode: mode to enter before running the command. To skip this step and execute directly in the cli's current mode, explicitly set this parameter to None. The default is "configure" :param output_expected: If not None, indicates whether output is expected (True) or no output is expected (False). If the opposite occurs, raise UnexpectedOutput. Default is None. :type output_expected: bool or None :param error_expected: If true, cli error output (with a leading '%') is expected and will be returned as regular output instead of raising a CLIError. Default is False, and error_expected always overrides output_expected. :type error_expected: bool :param interface: if mode 'subif', interface to configure 'gi0/1.666' or 'vlan 691' :type interface: string :param prompt: Prompt regex for matching unusual prompts. This should almost never be used as the ``mode`` parameter automatically handles all typical cases. This parameter is for unusual situations like the install config wizard. :raises CmdlineTimeout: on timeout :raises CLIError: if the output matches the cli's error format, and error output was not expected. :raises UnexpectedOutput: if output occurs when no output was expected, or no output occurs when output was expected :return: output of the command, minus the command itself. """ if output_expected is not None and not isinstance( output_expected, bool): raise TypeError("exec_command: output_expected requires a boolean " "value or None") if mode is not None: self.enter_mode(mode, interface) self._log.debug('Executing cmd "%s"' % command) if prompt is None: prompt = self.CLI_ANY_PROMPT (output, match_res) = self._send_line_and_wait(command, prompt, timeout=timeout) output = '\n'.join(output.splitlines()[1:]) if output and (re.match(self.CLI_ERROR_PROMPT, output)): if error_expected: # Skip output_expected processing entirely. return output else: try: mode = self.current_cli_mode() except exceptions.UnknownCLIMode: mode = '<unrecognized>' raise exceptions.CLIError(command, output=output, mode=mode) if ((output_expected is not None) and (bool(output) != bool(output_expected))): raise exceptions.UnexpectedOutput(command=command, output=output, expected_output=output_expected) return output