From 611261c4a0527f74cbff6f93d990cd907d7f13ae Mon Sep 17 00:00:00 2001 From: Dmitriyev <2291074+vdmitriyev@users.noreply.github.com> Date: Sat, 27 Jul 2024 17:47:19 +0200 Subject: [PATCH] ADD+FIX: add `--dry-run` option and fix lower case of `--table` option --- HISTORY.rst | 6 ++++++ README.md | 6 +++++- pymultissher/__main__.py | 37 +++++++++++++++++++++++++++++++++--- pymultissher/helpers.py | 1 - pymultissher/pymultissher.py | 31 +++++++++++++++++++++--------- pyproject.toml | 2 +- 6 files changed, 68 insertions(+), 15 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index e16cb60..249d37c 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -2,6 +2,12 @@ History ======= +0.3.10 (2024-07-27) +------------------ + +* Add `--dry-run` option to `run-batch`` and `run-command` +* Fix lower case of `--table` option + 0.3.9 (2024-07-23) ------------------ diff --git a/README.md b/README.md index c92cf63..0212de3 100644 --- a/README.md +++ b/README.md @@ -33,10 +33,14 @@ The utility also allows using custom YAML configuration files to store domain in ``` pymultissher --help ``` -* Utility could also be started as a Python module: +* Utility could also be started as a Python module ``` python -m pymultissher --help ``` +* Utility could also started with a `--dry-run`, which will not executed any commands over SSH + ``` + python -m pymultissher run-command --command "whoiam" --dry-run` + ``` ## Usage: CLI diff --git a/pymultissher/__main__.py b/pymultissher/__main__.py index cb8ac8e..3be7a85 100644 --- a/pymultissher/__main__.py +++ b/pymultissher/__main__.py @@ -28,12 +28,24 @@ from pymultissher.yamlhandler import YAMLEmptyConfigHandler, YAMLHandler app = typer.Typer(help=__description__) +console = Console() def __set_global_verbose(verbose: bool = False): set_verbose(verbose) +def __option_dry_run(dry_run): + if dry_run: + console.print(f"Option `--dry-run`: ", end=None) + console.print(f"enabled", style="yellow") + + +def __print_domain_name(domain): + console.print("Try to run commands on domain: ", end=None) + console.print(domain, style="yellow") + + @app.command() def run_batch( file_domains: Annotated[ @@ -56,6 +68,10 @@ def run_batch( Optional[bool], typer.Option(prompt=False, help="Verbose output"), ] = False, + dry_run: Annotated[ + Optional[bool], + typer.Option(prompt=False, help="Only shows prints. Commands will not be run"), + ] = False, ): """ Runs a batch of commands over SSH @@ -67,10 +83,12 @@ def run_batch( if file_commands is not None and not os.path.exists(file_commands): raise FileNotFoundError(f"File not found: {file_commands}") + view = view.lower() if view not in VIEW_CONSOLE_FORMATS: raise MultiSSHerNotSupported(f"Wrong view format for console. Allowed: {VIEW_CONSOLE_FORMATS}") __set_global_verbose(verbose) + __option_dry_run(dry_run) logger = get_logger() ssher = MultiSSHer(logger=logger) @@ -91,6 +109,7 @@ def run_batch( try: ssh_host = ssher.parse_hostname_item(item["domain"]) + __print_domain_name(ssh_host.domain) handle_dict_keys(ssher.data, key=ssh_host.domain) ssher.create_client(ssh_host) @@ -106,6 +125,7 @@ def run_batch( cmd=cmd["command"], category_name=cmd["report"]["category"], field_name=cmd["report"]["field"], + dry_run=dry_run, ) ssher.close_client() @@ -134,15 +154,27 @@ def run_command( Optional[str], typer.Option(prompt=False, help=f"View format:{VIEW_CONSOLE_FORMATS}"), ] = "json", + verbose: Annotated[ + Optional[bool], + typer.Option(prompt=False, help="Verbose output"), + ] = False, + dry_run: Annotated[ + Optional[bool], + typer.Option(prompt=False, help="Only shows prints. Commands will not be run"), + ] = False, ): """Runs a single command over SSH (default: whoami)""" if not os.path.exists(filedomains): raise FileNotFoundError(f"File not found: {filedomains}") + view = view.lower() if view not in VIEW_CONSOLE_FORMATS: raise MultiSSHerNotSupported(f"Wrong view format for console. Allowed: {VIEW_CONSOLE_FORMATS}") + __set_global_verbose(verbose) + __option_dry_run(dry_run) + logger = get_logger() ssher = MultiSSHer(logger=logger) @@ -154,6 +186,7 @@ def run_command( for item in filtered_domains: try: ssh_host = ssher.parse_hostname_item(item["domain"]) + __print_domain_name(ssh_host.domain) handle_dict_keys(ssher.data, key=ssh_host.domain) ssher.create_client(ssh_host) @@ -161,12 +194,12 @@ def run_command( raise MultiSSHerCreateClient("SSH client is not ready") if ssher.client is not None: - # run command ssher.run_command_over_ssh( hostname=ssh_host.domain, cmd=command, category_name="command", field_name=command.lower().replace(" ", "_"), + dry_run=dry_run, ) except Exception: @@ -238,7 +271,6 @@ def init( ): """Generates initial YAML configuration files with SSH defaults, domains and commands""" - logger = get_logger() console = Console() yml_handler = YAMLEmptyConfigHandler() @@ -278,7 +310,6 @@ def version( ] = False, ): """Shows package version""" - console = Console() if not verbose: console.print(f"Version: ", style="white", end=None) diff --git a/pymultissher/helpers.py b/pymultissher/helpers.py index b87877a..1e63afb 100644 --- a/pymultissher/helpers.py +++ b/pymultissher/helpers.py @@ -5,7 +5,6 @@ def handle_dict_keys(data: dict, key: str) -> None: data (dict): dictionary with data key (str): key of the dit to be checked """ - print(key) if key not in data: data[key] = {} diff --git a/pymultissher/pymultissher.py b/pymultissher/pymultissher.py index 2ae8461..c805f19 100644 --- a/pymultissher/pymultissher.py +++ b/pymultissher/pymultissher.py @@ -137,22 +137,35 @@ def close_client(self) -> None: if self.client is not None: self.client.close() - def run_command_over_ssh(self, hostname: str, cmd: str, category_name: str, field_name: str = "value"): + def run_command_over_ssh( + self, hostname: str, cmd: str, category_name: str, field_name: str = "value", dry_run: bool = False + ): """Runs command on the server. Args: - username (str): The username for SSH login. + hostname (str): host to be used + cmd (str): command to run + category_name (str): category for report + field_name (str, optional): filed name for report_. Defaults to "value". + dry_run (bool, optional): no run, only logs. Defaults to False. """ self.logger.debug(f"Run CMD on the server: {cmd}") - output = self.execute_cmd_and_read_response(cmd) - if output: - self.verbose_print(f"Server: '{hostname}'") - self.verbose_print(f"Output:\n{output}") - self.data[hostname][category_name] = {field_name: output} + + if not dry_run: + output = self.execute_cmd_and_read_response(cmd) + if output: + self.verbose_print(f"Server: '{hostname}'") + self.verbose_print(f"Output:\n{output}") + self.data[hostname][category_name] = {field_name: output} + else: + self.data[hostname][category_name] = {field_name: None} + self.logger.error(f"Error getting output. Domain: '{hostname}'. Command: '{cmd}'") else: - self.data[hostname][category_name] = {field_name: None} - self.logger.error(f"Error getting output. Domain: '{hostname}'. Command: '{cmd}'") + msg = "No execution of CMD on server, `--dry-run` option is enabled. CMD: " + self.logger.debug(f"msg: {dry_run}") + self.console.print(msg, end=None) + self.console.print(cmd, style="yellow") def execute_cmd_and_read_response(self, cmd: str) -> str: """Based on https://stackoverflow.com/questions/31834743/get-output-from-a-paramiko-ssh-exec-command-continuously diff --git a/pyproject.toml b/pyproject.toml index f71e0a0..0c02ec5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta" name = "pymultissher" description = "pymultissher is a simple CLI tool that runs commands on multiple servers at once using SSH" readme = "README.md" -version = "0.3.9" +version = "0.3.10" authors = [ {name = "vdmitriyev"} ]