Version 0.7.0 Implement command-line

- Now allows for command-line execution against hosts
This commit is contained in:
2025-10-13 22:00:53 -07:00
parent 614ea8b964
commit 81ea11e37d
4 changed files with 65 additions and 9 deletions

View File

@ -1,5 +1,9 @@
TODO TODO
- fix handling of control-c - We should throw out fabric; paramiko does everything we need - we can open a shell and send signals to handle control-C
and other sorts of situation handling. We'll basically have to rewrite how fabtools works but it should be relatively
trivial since we already basically handle everything at the paramiko level. We may have to parse sshconfig ourselves
but it shouldn't be too big a deal.
- fix handling of control-c (sorta works sometimes)
- redo the command loop to make some fuckin sense why do we use eceptions to do modal stuff - redo the command loop to make some fuckin sense why do we use eceptions to do modal stuff
- make styles optional - make styles optional
- expose styles in config file or personal config - expose styles in config file or personal config
@ -11,7 +15,6 @@ TODO
- add commands to show the list of captured outputs, along with the commands and targets - add commands to show the list of captured outputs, along with the commands and targets
- add commands to search captured outputs - add commands to search captured outputs
- add commands to search captured outputs and put them into a variable that can be used for /target etc. - add commands to search captured outputs and put them into a variable that can be used for /target etc.
- calling commands and aliases from command line (e.g. multiball -t <targets> -c <command>)
- tagging and filtering commands (so `noupgrade` gets a tag, and we can filter them) - tagging and filtering commands (so `noupgrade` gets a tag, and we can filter them)
- saving the last host set in a .file - saving the last host set in a .file
- allow server groups / server labels - allow server groups / server labels
@ -26,5 +29,9 @@ TODO
- implement interactive alias system - implement interactive alias system
- Catch more exceptions in fabtools, and also add retries - Catch more exceptions in fabtools, and also add retries
- Make the runner aware of multiple commands so that it can combine outputs and make 'overall success' or 'overall failure' - Make the runner aware of multiple commands so that it can combine outputs and make 'overall success' or 'overall failure'
- make C-c break the connections not the program - make C-c break the connections not the program (this requires throwing out fabric)
- make a /summary command that *only* outputs the summary of a remote command, not the stdout/stderr - make a /summary command that *only* outputs the summary of a remote command, not the stdout/stderr
- document more things about multiball
- allow a user-specific configuration that isn't in the current directory
- make a set of commands that modulate the /all list, so /all goes back to the real default not the total default
- the ability to deploy files

View File

@ -1 +1 @@
__version__ = "0.6.0" __version__ = "0.7.0"

View File

@ -31,6 +31,7 @@ from .fabtools import HostSet
from .tools import (load_bare_server_list, load_config, from .tools import (load_bare_server_list, load_config,
load_sshconfig_server_list) load_sshconfig_server_list)
oprint = print
from .style import print, STYLES from .style import print, STYLES
class MultiballCommandException(BaseException): class MultiballCommandException(BaseException):
@ -116,13 +117,13 @@ class Multiball:
self.ssh_config = Path(sc).expanduser() self.ssh_config = Path(sc).expanduser()
newhosts = load_sshconfig_server_list( newhosts = load_sshconfig_server_list(
Path(sc).expanduser()) Path(sc).expanduser())
print(f"<message>Loaded {len(newhosts)} from {sc}</message>") print(f"<message>Loaded {len(newhosts)} host(s) from {sc}</message>")
self.allhosts.update(newhosts) self.allhosts.update(newhosts)
if bareconfigs := config.get('hostlists'): if bareconfigs := config.get('hostlists'):
for sc in bareconfigs.split(','): for sc in bareconfigs.split(','):
newhosts = load_bare_server_list(Path(sc).expanduser()) newhosts = load_bare_server_list(Path(sc).expanduser())
print(f"<message>Loaded {len(newhosts)} from {sc}</message>") print(f"<message>Loaded {len(newhosts)} host(s) from {sc}</message>")
self.allhosts.update(newhosts) self.allhosts.update(newhosts)
else: else:
@ -417,12 +418,59 @@ class Multiball:
# Nothing of importance happens here. # Nothing of importance happens here.
... ...
def parse_args(argv):
if (len(argv) > 1):
parser = argparse.ArgumentParser(
prog='multiball',
description='Run commands on a number of hosts.',
epilog='With no arguments enter interactive mode.',
)
parser.add_argument('--confirm', action='store_true', help='Skip confirmation step, use with caution.')
parser.add_argument('-t', '--target', type=str, nargs='?', action='append', help='Select one or more targets.')
parser.add_argument('-a', '--all', action='store_true', help='Select default target list.')
parser.add_argument('-c', '--command', type=str, nargs='+', action='append', help='Command to run.')
res = parser.parse_args(argv)
return res
else:
return None
def multiball(*args, **kwargs):
def multiball(argv):
args = parse_args(argv)
## Command mode
if (args):
if (not args.all and (len(args.target) <= 0)):
print('Need either --all or at least one --target')
return 1
if (len(args.command) <= 0):
print('Need at least one --command')
return 1
print(f"<alert>Multiball {__version__}.</alert>")
mb = Multiball()
if (args.all):
mb.targethosts = mb.allhosts
else:
mb.targethosts = args.target
for command in args.command:
mb._print_targetlist()
command = ' '.join(command)
if (not args.confirm):
try:
mb.command_confirm('', args=f"Run `{command}`?")
except MBCommandAbort:
continue
mb._run_remote_command(command)
return 0
## Interactive mode
print(f"<alert>Welcome to Multiball {__version__}. type /help for help.</alert>") print(f"<alert>Welcome to Multiball {__version__}. type /help for help.</alert>")
mb = Multiball() mb = Multiball()
# loop on prompt
while (True): while (True):
# loop on prompt
try: try:
mb.cmd_prompt() mb.cmd_prompt()
except MBExit: except MBExit:
@ -431,7 +479,7 @@ def multiball(*args, **kwargs):
def main(): def main():
try: try:
sys.exit(multiball(sys.argv)) sys.exit(multiball(sys.argv[1:]))
except EOFError: except EOFError:
sys.exit(0) sys.exit(0)
except Exception as inst: except Exception as inst:

View File

@ -19,6 +19,7 @@ STYLES = Style.from_dict({
def print(message): def print(message):
message = message.replace("&", "&amp;")
print_formatted_text(HTML(f"<default>{message}</default>"), style=STYLES) print_formatted_text(HTML(f"<default>{message}</default>"), style=STYLES)