summaryrefslogtreecommitdiff
path: root/bin/ansible-console
diff options
context:
space:
mode:
Diffstat (limited to 'bin/ansible-console')
-rwxr-xr-xbin/ansible-console217
1 files changed, 153 insertions, 64 deletions
diff --git a/bin/ansible-console b/bin/ansible-console
index de7c4f5d..3125cc47 100755
--- a/bin/ansible-console
+++ b/bin/ansible-console
@@ -26,6 +26,7 @@ from ansible.module_utils._text import to_native, to_text
from ansible.module_utils.parsing.convert_bool import boolean
from ansible.parsing.splitter import parse_kv
from ansible.playbook.play import Play
+from ansible.plugins.list import list_plugins
from ansible.plugins.loader import module_loader, fragment_loader
from ansible.utils import plugin_docs
from ansible.utils.color import stringc
@@ -81,7 +82,6 @@ class ConsoleCLI(CLI, cmd.Cmd):
self.loader = None
self.passwords = dict()
- self.modules = None
self.cwd = '*'
# Defaults for these are set from the CLI in run()
@@ -93,6 +93,7 @@ class ConsoleCLI(CLI, cmd.Cmd):
self.diff = None
self.forks = None
self.task_timeout = None
+ self.collections = None
cmd.Cmd.__init__(self)
@@ -150,60 +151,30 @@ class ConsoleCLI(CLI, cmd.Cmd):
self.prompt = stringc(prompt, color, wrap_nonvisible_chars=True)
def list_modules(self):
- modules = set()
- if context.CLIARGS['module_path']:
- for path in context.CLIARGS['module_path']:
- if path:
- module_loader.add_directory(path)
+ return list_plugins('module', self.collections)
- module_paths = module_loader._get_paths()
- for path in module_paths:
- if path is not None:
- modules.update(self._find_modules_in_path(path))
- return modules
-
- def _find_modules_in_path(self, path):
-
- if os.path.isdir(path):
- for module in os.listdir(path):
- if module.startswith('.'):
- continue
- elif os.path.isdir(module):
- self._find_modules_in_path(module)
- elif module.startswith('__'):
- continue
- elif any(module.endswith(x) for x in C.REJECT_EXTS):
- continue
- elif module in C.IGNORE_FILES:
- continue
- elif module.startswith('_'):
- fullpath = '/'.join([path, module])
- if os.path.islink(fullpath): # avoids aliases
- continue
- module = module.replace('_', '', 1)
-
- module = os.path.splitext(module)[0] # removes the extension
- yield module
-
- def default(self, arg, forceshell=False):
+ def default(self, line, forceshell=False):
""" actually runs modules """
- if arg.startswith("#"):
+ if line.startswith("#"):
return False
if not self.cwd:
display.error("No host found")
return False
- if arg.split()[0] in self.modules:
- module = arg.split()[0]
- module_args = ' '.join(arg.split()[1:])
- else:
- module = 'shell'
- module_args = arg
-
- if forceshell is True:
- module = 'shell'
- module_args = arg
+ # defaults
+ module = 'shell'
+ module_args = line
+
+ if forceshell is not True:
+ possible_module, *possible_args = line.split()
+ if module_loader.find_plugin(possible_module):
+ # we found module!
+ module = possible_module
+ if possible_args:
+ module_args = ' '.join(possible_args)
+ else:
+ module_args = ''
if self.callback:
cb = self.callback
@@ -227,6 +198,7 @@ class ConsoleCLI(CLI, cmd.Cmd):
become_method=self.become_method,
check_mode=self.check_mode,
diff=self.diff,
+ collections=self.collections,
)
play = Play().load(play_ds, variable_manager=self.variable_manager, loader=self.loader)
except Exception as e:
@@ -249,6 +221,7 @@ class ConsoleCLI(CLI, cmd.Cmd):
)
result = self._tqm.run(play)
+ display.debug(result)
finally:
if self._tqm:
self._tqm.cleanup()
@@ -262,8 +235,10 @@ class ConsoleCLI(CLI, cmd.Cmd):
display.error('User interrupted execution')
return False
except Exception as e:
+ if self.verbosity >= 3:
+ import traceback
+ display.v(traceback.format_exc())
display.error(to_text(e))
- # FIXME: add traceback in very very verbose mode
return False
def emptyline(self):
@@ -283,21 +258,61 @@ class ConsoleCLI(CLI, cmd.Cmd):
"""
self.default(arg, True)
+ def help_shell(self):
+ display.display("You can run shell commands through the shell module.")
+
def do_forks(self, arg):
"""Set the number of forks"""
- if not arg:
- display.display('Usage: forks <number>')
- return
+ if arg:
+ try:
+ forks = int(arg)
+ except TypeError:
+ display.error('Invalid argument for "forks"')
+ self.usage_forks()
- forks = int(arg)
- if forks <= 0:
- display.display('forks must be greater than or equal to 1')
- return
+ if forks > 0:
+ self.forks = forks
+ self.set_prompt()
- self.forks = forks
- self.set_prompt()
+ else:
+ display.display('forks must be greater than or equal to 1')
+ else:
+ self.usage_forks()
+
+ def help_forks(self):
+ display.display("Set the number of forks to use per task")
+ self.usage_forks()
+
+ def usage_forks(self):
+ display.display('Usage: forks <number>')
do_serial = do_forks
+ help_serial = help_forks
+
+ def do_collections(self, arg):
+ """Set list of collections for 'short name' usage"""
+ if arg in ('', 'none'):
+ self.collections = None
+ elif not arg:
+ self.usage_collections()
+ else:
+ collections = arg.split(',')
+ for collection in collections:
+ if self.collections is None:
+ self.collections = []
+ self.collections.append(collection.strip())
+
+ if self.collections:
+ display.v('Collections name search is set to: %s' % ', '.join(self.collections))
+ else:
+ display.v('Collections name search is using defaults')
+
+ def help_collections(self):
+ display.display("Set the collection name search path when using short names for plugins")
+ self.usage_collections()
+
+ def usage_collections(self):
+ display.display('Usage: collections <collection1>[, <collection2> ...]\n Use empty quotes or "none" to reset to default.\n')
def do_verbosity(self, arg):
"""Set verbosity level"""
@@ -310,6 +325,9 @@ class ConsoleCLI(CLI, cmd.Cmd):
except (TypeError, ValueError) as e:
display.error('The verbosity must be a valid integer: %s' % to_text(e))
+ def help_verbosity(self):
+ display.display("Set the verbosity level, equivalent to -v for 1 and -vvvv for 4.")
+
def do_cd(self, arg):
"""
Change active host/group. You can use hosts patterns as well eg.:
@@ -330,14 +348,27 @@ class ConsoleCLI(CLI, cmd.Cmd):
self.set_prompt()
+ def help_cd(self):
+ display.display("Change active host/group. ")
+ self.usage_cd()
+
+ def usage_cd(self):
+ display.display("Usage: cd <group>|<host>|<host pattern>")
+
def do_list(self, arg):
"""List the hosts in the current group"""
- if arg == 'groups':
+ if not arg:
+ for host in self.selected:
+ display.display(host.name)
+ elif arg == 'groups':
for group in self.groups:
display.display(group)
else:
- for host in self.selected:
- display.display(host.name)
+ display.error('Invalid option passed to "list"')
+ self.help_list()
+
+ def help_list(self):
+ display.display("List the hosts in the current group or a list of groups if you add 'groups'.")
def do_become(self, arg):
"""Toggle whether plays run with become"""
@@ -348,6 +379,9 @@ class ConsoleCLI(CLI, cmd.Cmd):
else:
display.display("Please specify become value, e.g. `become yes`")
+ def help_become(self):
+ display.display("Toggle whether the tasks are run with become")
+
def do_remote_user(self, arg):
"""Given a username, set the remote user plays are run by"""
if arg:
@@ -356,6 +390,9 @@ class ConsoleCLI(CLI, cmd.Cmd):
else:
display.display("Please specify a remote user, e.g. `remote_user root`")
+ def help_remote_user(self):
+ display.display("Set the user for use as login to the remote target")
+
def do_become_user(self, arg):
"""Given a username, set the user that plays are run by when using become"""
if arg:
@@ -365,6 +402,9 @@ class ConsoleCLI(CLI, cmd.Cmd):
display.v("Current user is %s" % self.become_user)
self.set_prompt()
+ def help_become_user(self):
+ display.display("Set the user for use with privilege escalation (which remote user attempts to 'become' when become is enabled)")
+
def do_become_method(self, arg):
"""Given a become_method, set the privilege escalation method when using become"""
if arg:
@@ -374,6 +414,9 @@ class ConsoleCLI(CLI, cmd.Cmd):
display.display("Please specify a become_method, e.g. `become_method su`")
display.v("Current become_method is %s" % self.become_method)
+ def help_become_method(self):
+ display.display("Set the privilege escalation plugin to use when become is enabled")
+
def do_check(self, arg):
"""Toggle whether plays run with check mode"""
if arg:
@@ -383,6 +426,9 @@ class ConsoleCLI(CLI, cmd.Cmd):
display.display("Please specify check mode value, e.g. `check yes`")
display.v("check mode is currently %s." % self.check_mode)
+ def help_check(self):
+ display.display("Toggle check_mode for the tasks")
+
def do_diff(self, arg):
"""Toggle whether plays run with diff"""
if arg:
@@ -392,6 +438,9 @@ class ConsoleCLI(CLI, cmd.Cmd):
display.display("Please specify a diff value , e.g. `diff yes`")
display.v("diff mode is currently %s" % self.diff)
+ def help_diff(self):
+ display.display("Toggle diff output for the tasks")
+
def do_timeout(self, arg):
"""Set the timeout"""
if arg:
@@ -404,17 +453,28 @@ class ConsoleCLI(CLI, cmd.Cmd):
except (TypeError, ValueError) as e:
display.error('The timeout must be a valid positive integer, or 0 to disable: %s' % to_text(e))
else:
- display.display('Usage: timeout <seconds>')
+ self.usage_timeout()
+
+ def help_timeout(self):
+ display.display("Set task timeout in seconds")
+ self.usage_timeout()
+
+ def usage_timeout(self):
+ display.display('Usage: timeout <seconds>')
def do_exit(self, args):
"""Exits from the console"""
sys.stdout.write('\nAnsible-console was exited.\n')
return -1
+ def help_exit(self):
+ display.display("LEAVE!")
+
do_EOF = do_exit
+ help_EOF = help_exit
def helpdefault(self, module_name):
- if module_name in self.modules:
+ if module_name:
in_path = module_loader.find_plugin(module_name)
if in_path:
oc, a, _dummy1, _dummy2 = plugin_docs.get_docstring(in_path, fragment_loader)
@@ -428,6 +488,9 @@ class ConsoleCLI(CLI, cmd.Cmd):
else:
display.error('%s is not a valid command, use ? to list all valid commands.' % module_name)
+ def help_help(self):
+ display.warning("Don't be redundant!")
+
def complete_cd(self, text, line, begidx, endidx):
mline = line.partition(' ')[2]
offs = len(mline) - len(text)
@@ -440,7 +503,7 @@ class ConsoleCLI(CLI, cmd.Cmd):
return [to_native(s)[offs:] for s in completions if to_native(s).startswith(to_native(mline))]
def completedefault(self, text, line, begidx, endidx):
- if line.split()[0] in self.modules:
+ if line.split()[0] in self.list_modules():
mline = line.split(' ')[-1]
offs = len(mline) - len(text)
completions = self.module_args(line.split()[0])
@@ -473,7 +536,13 @@ class ConsoleCLI(CLI, cmd.Cmd):
self.forks = context.CLIARGS['forks']
self.task_timeout = context.CLIARGS['task_timeout']
- # dynamically add modules as commands
+ # set module path if needed
+ if context.CLIARGS['module_path']:
+ for path in context.CLIARGS['module_path']:
+ if path:
+ module_loader.add_directory(path)
+
+ # dynamically add 'cannonical' modules as commands, aliases coudld be used and dynamically loaded
self.modules = self.list_modules()
for module in self.modules:
setattr(self, 'do_' + module, lambda arg, module=module: self.default(module + ' ' + arg))
@@ -506,6 +575,26 @@ class ConsoleCLI(CLI, cmd.Cmd):
self.set_prompt()
self.cmdloop()
+ def __getattr__(self, name):
+ ''' handle not found to populate dynamically a module function if module matching name exists '''
+ attr = None
+
+ if name.startswith('do_'):
+ module = name.replace('do_', '')
+ if module_loader.find_plugin(module):
+ setattr(self, name, lambda arg, module=module: self.default(module + ' ' + arg))
+ attr = object.__getattr__(self, name)
+ elif name.startswith('help_'):
+ module = name.replace('help_', '')
+ if module_loader.find_plugin(module):
+ setattr(self, name, lambda module=module: self.helpdefault(module))
+ attr = object.__getattr__(self, name)
+
+ if attr is None:
+ raise AttributeError(f"{self.__class__} does not have a {name} attribute")
+
+ return attr
+
def main(args=None):
ConsoleCLI.cli_executor(args)