Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 71 additions & 1 deletion broker/commands.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Defines the CLI commands for Broker."""

import contextlib
from functools import wraps
import logging
import signal
Expand All @@ -11,6 +12,7 @@

setup_logging(console_level=logging.INFO) # Basic setup until settings are loaded

from click_shell import shell
from rich.console import Console
from rich.syntax import Syntax
from rich.table import Table
Expand Down Expand Up @@ -40,7 +42,7 @@
click.rich_click.COMMAND_GROUPS = {
"broker": [
{"name": "Core Actions", "commands": ["checkout", "checkin", "inventory"]},
{"name": "Extras", "commands": ["execute", "extend", "providers", "config"]},
{"name": "Extras", "commands": ["execute", "extend", "providers", "config", "shell"]},
]
}

Expand Down Expand Up @@ -564,3 +566,71 @@ def validate(chunk):
logger.info("Validation passed!")
except exceptions.BrokerError as err:
logger.warning(f"Validation failed: {err}")


def _make_shell_help_func(cmd, shell_instance):
"""Create a help function that invokes the command with --help.

This works around a compatibility issue between click_shell and rich_click where
the shell's built-in help system uses a standard HelpFormatter that lacks
rich_click's config attribute.
"""

def help_func():
# Invoke the command with --help which properly uses rich_click formatting
with contextlib.suppress(SystemExit):
cmd.main(["--help"], standalone_mode=False, parent=shell_instance.ctx)

help_func.__name__ = f"help_{cmd.name}"
return help_func


@shell(
prompt="broker > ",
intro="Welcome to Broker's interactive shell.\nType 'help' for commands, 'exit' or 'quit' to leave.",
)
def broker_shell():
"""Start an interactive Broker shell session."""
pass


# Register commands to the shell
broker_shell.add_command(checkout)
broker_shell.add_command(checkin)
broker_shell.add_command(inventory)
broker_shell.add_command(execute)
broker_shell.add_command(providers)
broker_shell.add_command(config)


# Shell-only commands (not available as normal sub-commands)
@broker_shell.command(name="reload_config")
def reload_config_cmd():
"""Reload Broker's configuration from disk.

This clears the cached settings, forcing them to be re-read
from the settings file on next access.
"""
settings.settings._settings = None
setup_logging(
console_level=settings.settings.logging.console_level,
file_level=settings.settings.logging.file_level,
log_path=settings.settings.logging.log_path,
structured=settings.settings.logging.structured,
)
CONSOLE.print("Configuration reloaded.")


# Patch help functions on the shell instance to work around click_shell/rich_click incompatibility
for cmd_name, cmd in broker_shell.commands.items():
setattr(broker_shell.shell, f"help_{cmd_name}", _make_shell_help_func(cmd, broker_shell.shell))


@cli.command(name="shell")
def shell_cmd():
"""Start an interactive Broker shell session.

This provides a REPL-like interface for running Broker commands
without needing to prefix each with 'broker'.
"""
broker_shell(standalone_mode=False, args=[])
7 changes: 5 additions & 2 deletions broker/providers/ansible_tower.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from packaging.version import InvalidVersion, Version
from requests.exceptions import ConnectionError
from rich.console import Console
from rich.progress import track
from rich.prompt import Prompt
from ruamel.yaml import YAML, YAMLError

Expand Down Expand Up @@ -903,8 +904,10 @@ def get_inventory(self, user=None):
for inv in invs:
inv_hosts = inv.get_related("hosts", page_size=200).results
hosts.extend(inv_hosts)
with click.progressbar(hosts, label="Compiling host information") as hosts_bar:
compiled_host_info = [self._compile_host_info(host) for host in hosts_bar]
compiled_host_info = [
self._compile_host_info(host)
for host in track(hosts, description="Compiling host information")
]
return compiled_host_info

def extend(self, target_vm, new_expire_time=None, provider_labels=None):
Expand Down
7 changes: 5 additions & 2 deletions broker/providers/beaker.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import click
from dynaconf import Validator
from rich.progress import track

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -158,6 +159,8 @@ def extend(self, host_name, extend_duration=99):
def get_inventory(self, *args):
"""Get a list of hosts and their information from Beaker."""
hosts = self.runtime.user_systems()
with click.progressbar(hosts, label="Compiling host information") as hosts_bar:
compiled_host_info = [self._compile_host_info(host) for host in hosts_bar]
compiled_host_info = [
self._compile_host_info(host)
for host in track(hosts, description="Compiling host information")
]
return compiled_host_info
5 changes: 3 additions & 2 deletions broker/providers/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import click
from dynaconf import Validator
from rich.progress import track

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -261,10 +262,10 @@ def provider_help(
def get_inventory(self, name_prefix):
"""Get all containers that have a matching name prefix."""
name_prefix = name_prefix or self._name_prefix
containers = [cont for cont in self.runtime.containers if cont.name.startswith(name_prefix)]
return [
container_info(cont)
for cont in self.runtime.containers
if cont.name.startswith(name_prefix)
for cont in track(containers, description="Compiling host information")
]

def extend(self):
Expand Down
7 changes: 5 additions & 2 deletions broker/providers/foreman.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import click
from dynaconf import Validator
from rich.progress import track

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -195,8 +196,10 @@ def _compile_host_info(self, host):
def get_inventory(self, *args, **kwargs):
"""Synchronize list of hosts on Foreman using set prefix."""
all_hosts = self.runtime.hosts()
with click.progressbar(all_hosts, label="Compiling host information") as hosts_bar:
compiled_host_info = [self._compile_host_info(host) for host in hosts_bar]
compiled_host_info = [
self._compile_host_info(host)
for host in track(all_hosts, description="Compiling host information")
]
return compiled_host_info

def _host_release(self):
Expand Down
9 changes: 5 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ dependencies = [
"rich",
"rich_click",
"ruamel.yaml",
"click-shell",
]

[project.urls]
Expand All @@ -47,7 +48,7 @@ dev = [
"pytest-randomly",
"ruff",
"tox",
"tox-uv"
"tox-uv",
]
docker = ["docker", "paramiko"]
hussh = ["hussh>=0.1.7"]
Expand Down Expand Up @@ -200,9 +201,9 @@ known-first-party = ["broker"]
combine-as-imports = true

[tool.ruff.lint.per-file-ignores]
"broker/commands.py" = ["E402"] # Intentional: logging setup before imports
"broker/providers/*.py" = ["E402"] # logger defined before other imports
"broker/binds/*.py" = ["E402"] # logger defined before other imports
"broker/commands.py" = ["E402"] # Intentional: logging setup before imports
"broker/providers/*.py" = ["E402"] # logger defined before other imports
"broker/binds/*.py" = ["E402"] # logger defined before other imports

[tool.ruff.lint.mccabe]
max-complexity = 25
Loading