Version 0.7.0 Implement command-line
- Now allows for command-line execution against hosts
This commit is contained in:
13
TODO.txt
13
TODO.txt
@ -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
|
||||||
|
@ -1 +1 @@
|
|||||||
__version__ = "0.6.0"
|
__version__ = "0.7.0"
|
||||||
|
@ -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:
|
||||||
|
@ -19,6 +19,7 @@ STYLES = Style.from_dict({
|
|||||||
|
|
||||||
|
|
||||||
def print(message):
|
def print(message):
|
||||||
|
message = message.replace("&", "&")
|
||||||
print_formatted_text(HTML(f"<default>{message}</default>"), style=STYLES)
|
print_formatted_text(HTML(f"<default>{message}</default>"), style=STYLES)
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user