Skip to content

Commit

Permalink
ADD+FIX: add --dry-run option and fix lower case of --table option
Browse files Browse the repository at this point in the history
  • Loading branch information
vdmitriyev committed Jul 27, 2024
1 parent 37f7762 commit 611261c
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 15 deletions.
6 changes: 6 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
------------------

Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 34 additions & 3 deletions pymultissher/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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[
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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)

Expand All @@ -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()
Expand Down Expand Up @@ -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)

Expand All @@ -154,19 +186,20 @@ 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)

if ssher.client is None:
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:
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -278,7 +310,6 @@ def version(
] = False,
):
"""Shows package version"""
console = Console()

if not verbose:
console.print(f"Version: ", style="white", end=None)
Expand Down
1 change: 0 additions & 1 deletion pymultissher/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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] = {}

Expand Down
31 changes: 22 additions & 9 deletions pymultissher/pymultissher.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
]
Expand Down

0 comments on commit 611261c

Please sign in to comment.