Migrate cli and dependencies to use global display

This commit is contained in:
Toshio Kuratomi 2015-11-10 11:40:55 -08:00
parent 1b7d3f2898
commit 318bfbb207
12 changed files with 167 additions and 131 deletions

View file

@ -79,7 +79,7 @@ if __name__ == '__main__':
else: else:
raise raise
cli = mycli(sys.argv, display=display) cli = mycli(sys.argv)
cli.parse() cli.parse()
sys.exit(cli.run()) sys.exit(cli.run())

View file

@ -33,7 +33,14 @@ from ansible import __version__
from ansible import constants as C from ansible import constants as C
from ansible.errors import AnsibleError, AnsibleOptionsError from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.utils.unicode import to_bytes from ansible.utils.unicode import to_bytes
from ansible.utils.display import Display
try:
from __main__ import display
display = display
except ImportError:
from ansible.utils.display import Display
display = Display()
class SortedOptParser(optparse.OptionParser): class SortedOptParser(optparse.OptionParser):
'''Optparser which sorts the options by opt before outputting --help''' '''Optparser which sorts the options by opt before outputting --help'''
@ -44,6 +51,7 @@ class SortedOptParser(optparse.OptionParser):
self.option_list.sort(key=operator.methodcaller('get_opt_string')) self.option_list.sort(key=operator.methodcaller('get_opt_string'))
return optparse.OptionParser.format_help(self, formatter=None) return optparse.OptionParser.format_help(self, formatter=None)
class CLI(object): class CLI(object):
''' code behind bin/ansible* programs ''' ''' code behind bin/ansible* programs '''
@ -59,7 +67,7 @@ class CLI(object):
LESS_OPTS = 'FRSX' # -F (quit-if-one-screen) -R (allow raw ansi control chars) LESS_OPTS = 'FRSX' # -F (quit-if-one-screen) -R (allow raw ansi control chars)
# -S (chop long lines) -X (disable termcap init and de-init) # -S (chop long lines) -X (disable termcap init and de-init)
def __init__(self, args, display=None): def __init__(self, args):
""" """
Base init method for all command line programs Base init method for all command line programs
""" """
@ -69,11 +77,6 @@ class CLI(object):
self.parser = None self.parser = None
self.action = None self.action = None
if display is None:
self.display = Display()
else:
self.display = display
def set_action(self): def set_action(self):
""" """
Get the action the user wants to execute from the sys argv list. Get the action the user wants to execute from the sys argv list.
@ -102,9 +105,9 @@ class CLI(object):
if self.options.verbosity > 0: if self.options.verbosity > 0:
if C.CONFIG_FILE: if C.CONFIG_FILE:
self.display.display("Using %s as config file" % C.CONFIG_FILE) display.display("Using %s as config file" % C.CONFIG_FILE)
else: else:
self.display.display("No config file found; using defaults") display.display("No config file found; using defaults")
@staticmethod @staticmethod
def ask_vault_passwords(ask_new_vault_pass=False, rekey=False): def ask_vault_passwords(ask_new_vault_pass=False, rekey=False):
@ -135,7 +138,6 @@ class CLI(object):
return vault_pass, new_vault_pass return vault_pass, new_vault_pass
def ask_passwords(self): def ask_passwords(self):
''' prompt for connection and become passwords if needed ''' ''' prompt for connection and become passwords if needed '''
@ -164,7 +166,6 @@ class CLI(object):
return (sshpass, becomepass) return (sshpass, becomepass)
def normalize_become_options(self): def normalize_become_options(self):
''' this keeps backwards compatibility with sudo/su self.options ''' ''' this keeps backwards compatibility with sudo/su self.options '''
self.options.become_ask_pass = self.options.become_ask_pass or self.options.ask_sudo_pass or self.options.ask_su_pass or C.DEFAULT_BECOME_ASK_PASS self.options.become_ask_pass = self.options.become_ask_pass or self.options.ask_sudo_pass or self.options.ask_su_pass or C.DEFAULT_BECOME_ASK_PASS
@ -179,7 +180,6 @@ class CLI(object):
self.options.become = True self.options.become = True
self.options.become_method = 'su' self.options.become_method = 'su'
def validate_conflicts(self, vault_opts=False, runas_opts=False, fork_opts=False): def validate_conflicts(self, vault_opts=False, runas_opts=False, fork_opts=False):
''' check for conflicting options ''' ''' check for conflicting options '''
@ -190,7 +190,6 @@ class CLI(object):
if (op.ask_vault_pass and op.vault_password_file): if (op.ask_vault_pass and op.vault_password_file):
self.parser.error("--ask-vault-pass and --vault-password-file are mutually exclusive") self.parser.error("--ask-vault-pass and --vault-password-file are mutually exclusive")
if runas_opts: if runas_opts:
# Check for privilege escalation conflicts # Check for privilege escalation conflicts
if (op.su or op.su_user or op.ask_su_pass) and \ if (op.su or op.su_user or op.ask_su_pass) and \
@ -215,7 +214,7 @@ class CLI(object):
@staticmethod @staticmethod
def base_parser(usage="", output_opts=False, runas_opts=False, meta_opts=False, runtask_opts=False, vault_opts=False, module_opts=False, def base_parser(usage="", output_opts=False, runas_opts=False, meta_opts=False, runtask_opts=False, vault_opts=False, module_opts=False,
async_opts=False, connect_opts=False, subset_opts=False, check_opts=False, inventory_opts=False, epilog=None, fork_opts=False): async_opts=False, connect_opts=False, subset_opts=False, check_opts=False, inventory_opts=False, epilog=None, fork_opts=False):
''' create an options parser for most ansible scripts ''' ''' create an options parser for most ansible scripts '''
# TODO: implement epilog parsing # TODO: implement epilog parsing
@ -257,7 +256,6 @@ class CLI(object):
parser.add_option('--output', default=None, dest='output_file', parser.add_option('--output', default=None, dest='output_file',
help='output file name for encrypt or decrypt; use - for stdout') help='output file name for encrypt or decrypt; use - for stdout')
if subset_opts: if subset_opts:
parser.add_option('-t', '--tags', dest='tags', default='all', parser.add_option('-t', '--tags', dest='tags', default='all',
help="only run plays and tasks tagged with these values") help="only run plays and tasks tagged with these values")
@ -295,7 +293,6 @@ class CLI(object):
parser.add_option('--ask-become-pass', default=False, dest='become_ask_pass', action='store_true', parser.add_option('--ask-become-pass', default=False, dest='become_ask_pass', action='store_true',
help='ask for privilege escalation password') help='ask for privilege escalation password')
if connect_opts: if connect_opts:
parser.add_option('-k', '--ask-pass', default=C.DEFAULT_ASK_PASS, dest='ask_pass', action='store_true', parser.add_option('-k', '--ask-pass', default=C.DEFAULT_ASK_PASS, dest='ask_pass', action='store_true',
help='ask for connection password') help='ask for connection password')
@ -427,7 +424,7 @@ class CLI(object):
result = CLI._git_repo_info(repo_path) result = CLI._git_repo_info(repo_path)
submodules = os.path.join(basedir, '.gitmodules') submodules = os.path.join(basedir, '.gitmodules')
if not os.path.exists(submodules): if not os.path.exists(submodules):
return result return result
f = open(submodules) f = open(submodules)
for line in f: for line in f:
tokens = line.strip().split(' ') tokens = line.strip().split(' ')
@ -440,21 +437,20 @@ class CLI(object):
f.close() f.close()
return result return result
def pager(self, text): def pager(self, text):
''' find reasonable way to display text ''' ''' find reasonable way to display text '''
# this is a much simpler form of what is in pydoc.py # this is a much simpler form of what is in pydoc.py
if not sys.stdout.isatty(): if not sys.stdout.isatty():
self.display.display(text) display.display(text)
elif 'PAGER' in os.environ: elif 'PAGER' in os.environ:
if sys.platform == 'win32': if sys.platform == 'win32':
self.display.display(text) display.display(text)
else: else:
self.pager_pipe(text, os.environ['PAGER']) self.pager_pipe(text, os.environ['PAGER'])
elif subprocess.call('(less --version) 2> /dev/null', shell = True) == 0: elif subprocess.call('(less --version) 2> /dev/null', shell = True) == 0:
self.pager_pipe(text, 'less') self.pager_pipe(text, 'less')
else: else:
self.display.display(text) display.display(text)
@staticmethod @staticmethod
def pager_pipe(text, cmd): def pager_pipe(text, cmd):
@ -521,4 +517,3 @@ class CLI(object):
if os.pathsep in data: if os.pathsep in data:
data = data.split(os.pathsep)[0] data = data.split(os.pathsep)[0]
return data return data

View file

@ -34,6 +34,14 @@ from ansible.plugins import get_all_plugin_loaders
from ansible.utils.vars import load_extra_vars from ansible.utils.vars import load_extra_vars
from ansible.vars import VariableManager from ansible.vars import VariableManager
try:
from __main__ import display
display = display
except ImportError:
from ansible.utils.display import Display
display = Display()
######################################################## ########################################################
class AdHocCLI(CLI): class AdHocCLI(CLI):
@ -68,7 +76,7 @@ class AdHocCLI(CLI):
if len(self.args) != 1: if len(self.args) != 1:
raise AnsibleOptionsError("Missing target hosts") raise AnsibleOptionsError("Missing target hosts")
self.display.verbosity = self.options.verbosity display.verbosity = self.options.verbosity
self.validate_conflicts(runas_opts=True, vault_opts=True, fork_opts=True) self.validate_conflicts(runas_opts=True, vault_opts=True, fork_opts=True)
return True return True
@ -86,7 +94,6 @@ class AdHocCLI(CLI):
super(AdHocCLI, self).run() super(AdHocCLI, self).run()
# only thing left should be host pattern # only thing left should be host pattern
pattern = self.args[0] pattern = self.args[0]
@ -121,7 +128,7 @@ class AdHocCLI(CLI):
hosts = inventory.list_hosts(pattern) hosts = inventory.list_hosts(pattern)
no_hosts = False no_hosts = False
if len(hosts) == 0: if len(hosts) == 0:
self.display.warning("provided hosts list is empty, only localhost is available") display.warning("provided hosts list is empty, only localhost is available")
no_hosts = True no_hosts = True
if self.options.subset: if self.options.subset:
@ -131,9 +138,9 @@ class AdHocCLI(CLI):
raise AnsibleError("Specified --limit does not match any hosts") raise AnsibleError("Specified --limit does not match any hosts")
if self.options.listhosts: if self.options.listhosts:
self.display.display(' hosts (%d):' % len(hosts)) display.display(' hosts (%d):' % len(hosts))
for host in hosts: for host in hosts:
self.display.display(' %s' % host) display.display(' %s' % host)
return 0 return 0
if self.options.module_name in C.MODULE_REQUIRE_ARGS and not self.options.module_args: if self.options.module_name in C.MODULE_REQUIRE_ARGS and not self.options.module_args:
@ -168,7 +175,6 @@ class AdHocCLI(CLI):
inventory=inventory, inventory=inventory,
variable_manager=variable_manager, variable_manager=variable_manager,
loader=loader, loader=loader,
display=self.display,
options=self.options, options=self.options,
passwords=passwords, passwords=passwords,
stdout_callback=cb, stdout_callback=cb,

View file

@ -31,15 +31,23 @@ from ansible.plugins import module_loader
from ansible.cli import CLI from ansible.cli import CLI
from ansible.utils import module_docs from ansible.utils import module_docs
try:
from __main__ import display
display = display
except ImportError:
from ansible.utils.display import Display
display = Display()
class DocCLI(CLI): class DocCLI(CLI):
""" Vault command line class """ """ Vault command line class """
BLACKLIST_EXTS = ('.pyc', '.swp', '.bak', '~', '.rpm', '.md', '.txt') BLACKLIST_EXTS = ('.pyc', '.swp', '.bak', '~', '.rpm', '.md', '.txt')
IGNORE_FILES = [ "COPYING", "CONTRIBUTING", "LICENSE", "README", "VERSION", "GUIDELINES", "test-docs.sh"] IGNORE_FILES = [ "COPYING", "CONTRIBUTING", "LICENSE", "README", "VERSION", "GUIDELINES", "test-docs.sh"]
def __init__(self, args, display=None): def __init__(self, args):
super(DocCLI, self).__init__(args, display) super(DocCLI, self).__init__(args)
self.module_list = [] self.module_list = []
def parse(self): def parse(self):
@ -56,8 +64,7 @@ class DocCLI(CLI):
help='Show playbook snippet for specified module(s)') help='Show playbook snippet for specified module(s)')
self.options, self.args = self.parser.parse_args() self.options, self.args = self.parser.parse_args()
self.display.verbosity = self.options.verbosity display.verbosity = self.options.verbosity
def run(self): def run(self):
@ -86,7 +93,7 @@ class DocCLI(CLI):
try: try:
filename = module_loader.find_plugin(module) filename = module_loader.find_plugin(module)
if filename is None: if filename is None:
self.display.warning("module %s not found in %s\n" % (module, DocCLI.print_paths(module_loader))) display.warning("module %s not found in %s\n" % (module, DocCLI.print_paths(module_loader)))
continue continue
if any(filename.endswith(x) for x in self.BLACKLIST_EXTS): if any(filename.endswith(x) for x in self.BLACKLIST_EXTS):
@ -95,8 +102,8 @@ class DocCLI(CLI):
try: try:
doc, plainexamples, returndocs = module_docs.get_docstring(filename, verbose=(self.options.verbosity > 0)) doc, plainexamples, returndocs = module_docs.get_docstring(filename, verbose=(self.options.verbosity > 0))
except: except:
self.display.vvv(traceback.print_exc()) display.vvv(traceback.print_exc())
self.display.error("module %s has a documentation error formatting or is missing documentation\nTo see exact traceback use -vvv" % module) display.error("module %s has a documentation error formatting or is missing documentation\nTo see exact traceback use -vvv" % module)
continue continue
if doc is not None: if doc is not None:
@ -122,7 +129,7 @@ class DocCLI(CLI):
# probably a quoting issue. # probably a quoting issue.
raise AnsibleError("Parsing produced an empty object.") raise AnsibleError("Parsing produced an empty object.")
except Exception as e: except Exception as e:
self.display.vvv(traceback.print_exc()) display.vvv(traceback.print_exc())
raise AnsibleError("module %s missing documentation (or could not parse documentation): %s\n" % (module, str(e))) raise AnsibleError("module %s missing documentation (or could not parse documentation): %s\n" % (module, str(e)))
self.pager(text) self.pager(text)
@ -150,9 +157,8 @@ class DocCLI(CLI):
module = os.path.splitext(module)[0] # removes the extension module = os.path.splitext(module)[0] # removes the extension
self.module_list.append(module) self.module_list.append(module)
def get_module_list_text(self): def get_module_list_text(self):
columns = self.display.columns columns = display.columns
displace = max(len(x) for x in self.module_list) displace = max(len(x) for x in self.module_list)
linelimit = columns - displace - 5 linelimit = columns - displace - 5
text = [] text = []
@ -189,7 +195,6 @@ class DocCLI(CLI):
text.extend(deprecated) text.extend(deprecated)
return "\n".join(text) return "\n".join(text)
@staticmethod @staticmethod
def print_paths(finder): def print_paths(finder):
''' Returns a string suitable for printing of the search path ''' ''' Returns a string suitable for printing of the search path '''
@ -209,7 +214,7 @@ class DocCLI(CLI):
text.append(" action: %s" % (doc['module'])) text.append(" action: %s" % (doc['module']))
pad = 31 pad = 31
subdent = ''.join([" " for a in xrange(pad)]) subdent = ''.join([" " for a in xrange(pad)])
limit = self.display.columns - pad limit = display.columns - pad
for o in sorted(doc['options'].keys()): for o in sorted(doc['options'].keys()):
opt = doc['options'][o] opt = doc['options'][o]
@ -229,8 +234,8 @@ class DocCLI(CLI):
opt_indent=" " opt_indent=" "
text = [] text = []
text.append("> %s\n" % doc['module'].upper()) text.append("> %s\n" % doc['module'].upper())
pad = self.display.columns * 0.20 pad = display.columns * 0.20
limit = max(self.display.columns - int(pad), 70) limit = max(display.columns - int(pad), 70)
if isinstance(doc['description'], list): if isinstance(doc['description'], list):
desc = " ".join(doc['description']) desc = " ".join(doc['description'])

View file

@ -28,7 +28,6 @@ import sys
import yaml import yaml
from collections import defaultdict from collections import defaultdict
from distutils.version import LooseVersion
from jinja2 import Environment from jinja2 import Environment
import ansible.constants as C import ansible.constants as C
@ -39,16 +38,24 @@ from ansible.galaxy.api import GalaxyAPI
from ansible.galaxy.role import GalaxyRole from ansible.galaxy.role import GalaxyRole
from ansible.playbook.role.requirement import RoleRequirement from ansible.playbook.role.requirement import RoleRequirement
try:
from __main__ import display
display = display
except ImportError:
from ansible.utils.display import Display
display = Display()
class GalaxyCLI(CLI): class GalaxyCLI(CLI):
VALID_ACTIONS = ("init", "info", "install", "list", "remove", "search") VALID_ACTIONS = ("init", "info", "install", "list", "remove", "search")
SKIP_INFO_KEYS = ("name", "description", "readme_html", "related", "summary_fields", "average_aw_composite", "average_aw_score", "url" ) SKIP_INFO_KEYS = ("name", "description", "readme_html", "related", "summary_fields", "average_aw_composite", "average_aw_score", "url" )
def __init__(self, args, display=None): def __init__(self, args):
self.api = None self.api = None
self.galaxy = None self.galaxy = None
super(GalaxyCLI, self).__init__(args, display) super(GalaxyCLI, self).__init__(args)
def parse(self): def parse(self):
''' create an options parser for bin/ansible ''' ''' create an options parser for bin/ansible '''
@ -58,7 +65,6 @@ class GalaxyCLI(CLI):
epilog = "\nSee '%s <command> --help' for more information on a specific command.\n\n" % os.path.basename(sys.argv[0]) epilog = "\nSee '%s <command> --help' for more information on a specific command.\n\n" % os.path.basename(sys.argv[0])
) )
self.set_action() self.set_action()
# options specific to actions # options specific to actions
@ -109,8 +115,8 @@ class GalaxyCLI(CLI):
# get options, args and galaxy object # get options, args and galaxy object
self.options, self.args =self.parser.parse_args() self.options, self.args =self.parser.parse_args()
self.display.verbosity = self.options.verbosity display.verbosity = self.options.verbosity
self.galaxy = Galaxy(self.options, self.display) self.galaxy = Galaxy(self.options)
return True return True
@ -178,10 +184,10 @@ class GalaxyCLI(CLI):
if os.path.isfile(role_path): if os.path.isfile(role_path):
raise AnsibleError("- the path %s already exists, but is a file - aborting" % role_path) raise AnsibleError("- the path %s already exists, but is a file - aborting" % role_path)
elif not force: elif not force:
raise AnsibleError("- the directory %s already exists." % role_path + \ raise AnsibleError("- the directory %s already exists."
"you can use --force to re-initialize this directory,\n" + \ "you can use --force to re-initialize this directory,\n"
"however it will reset any main.yml files that may have\n" + \ "however it will reset any main.yml files that may have\n"
"been modified there already.") "been modified there already." % role_path)
# create the default README.md # create the default README.md
if not os.path.exists(role_path): if not os.path.exists(role_path):
@ -234,7 +240,7 @@ class GalaxyCLI(CLI):
f = open(main_yml_path, 'w') f = open(main_yml_path, 'w')
f.write('---\n# %s file for %s\n' % (dir,role_name)) f.write('---\n# %s file for %s\n' % (dir,role_name))
f.close() f.close()
self.display.display("- %s was created successfully" % role_name) display.display("- %s was created successfully" % role_name)
def execute_info(self): def execute_info(self):
""" """
@ -297,7 +303,7 @@ class GalaxyCLI(CLI):
# the user needs to specify one of either --role-file # the user needs to specify one of either --role-file
# or specify a single user/role name # or specify a single user/role name
raise AnsibleOptionsError("- you must specify a user/role name or a roles file") raise AnsibleOptionsError("- you must specify a user/role name or a roles file")
elif len(self.args) == 1 and not role_file is None: elif len(self.args) == 1 and role_file is not None:
# using a role file is mutually exclusive of specifying # using a role file is mutually exclusive of specifying
# the role name on the command line # the role name on the command line
raise AnsibleOptionsError("- please specify a user/role name, or a roles file, but not both") raise AnsibleOptionsError("- please specify a user/role name, or a roles file, but not both")
@ -320,22 +326,22 @@ class GalaxyCLI(CLI):
for role in required_roles: for role in required_roles:
role = RoleRequirement.role_yaml_parse(role) role = RoleRequirement.role_yaml_parse(role)
self.display.debug('found role %s in yaml file' % str(role)) display.debug('found role %s in yaml file' % str(role))
if 'name' not in role and 'scm' not in role: if 'name' not in role and 'scm' not in role:
raise AnsibleError("Must specify name or src for role") raise AnsibleError("Must specify name or src for role")
roles_left.append(GalaxyRole(self.galaxy, **role)) roles_left.append(GalaxyRole(self.galaxy, **role))
else: else:
self.display.deprecated("going forward only the yaml format will be supported") display.deprecated("going forward only the yaml format will be supported")
# roles listed in a file, one per line # roles listed in a file, one per line
for rline in f.readlines(): for rline in f.readlines():
if rline.startswith("#") or rline.strip() == '': if rline.startswith("#") or rline.strip() == '':
continue continue
self.display.debug('found role %s in text file' % str(rline)) display.debug('found role %s in text file' % str(rline))
role = RoleRequirement.role_yaml_parse(rline.strip()) role = RoleRequirement.role_yaml_parse(rline.strip())
roles_left.append(GalaxyRole(self.galaxy, **role)) roles_left.append(GalaxyRole(self.galaxy, **role))
f.close() f.close()
except (IOError, OSError) as e: except (IOError, OSError) as e:
self.display.error('Unable to open %s: %s' % (role_file, str(e))) display.error('Unable to open %s: %s' % (role_file, str(e)))
else: else:
# roles were specified directly, so we'll just go out grab them # roles were specified directly, so we'll just go out grab them
# (and their dependencies, unless the user doesn't want us to). # (and their dependencies, unless the user doesn't want us to).
@ -343,18 +349,17 @@ class GalaxyCLI(CLI):
roles_left.append(GalaxyRole(self.galaxy, rname.strip())) roles_left.append(GalaxyRole(self.galaxy, rname.strip()))
for role in roles_left: for role in roles_left:
self.display.debug('Installing role %s ' % role.name) display.debug('Installing role %s ' % role.name)
# query the galaxy API for the role data # query the galaxy API for the role data
role_data = None
if role.install_info is not None and not force: if role.install_info is not None and not force:
self.display.display('- %s is already installed, skipping.' % role.name) display.display('- %s is already installed, skipping.' % role.name)
continue continue
try: try:
installed = role.install() installed = role.install()
except AnsibleError as e: except AnsibleError as e:
self.display.warning("- %s was NOT installed successfully: %s " % (role.name, str(e))) display.warning("- %s was NOT installed successfully: %s " % (role.name, str(e)))
self.exit_without_ignore() self.exit_without_ignore()
continue continue
@ -362,7 +367,7 @@ class GalaxyCLI(CLI):
if not no_deps and installed: if not no_deps and installed:
role_dependencies = role.metadata.get('dependencies') or [] role_dependencies = role.metadata.get('dependencies') or []
for dep in role_dependencies: for dep in role_dependencies:
self.display.debug('Installing dep %s' % dep) display.debug('Installing dep %s' % dep)
dep_req = RoleRequirement() dep_req = RoleRequirement()
dep_info = dep_req.role_yaml_parse(dep) dep_info = dep_req.role_yaml_parse(dep)
dep_role = GalaxyRole(self.galaxy, **dep_info) dep_role = GalaxyRole(self.galaxy, **dep_info)
@ -372,15 +377,15 @@ class GalaxyCLI(CLI):
continue continue
if dep_role.install_info is None or force: if dep_role.install_info is None or force:
if dep_role not in roles_left: if dep_role not in roles_left:
self.display.display('- adding dependency: %s' % dep_role.name) display.display('- adding dependency: %s' % dep_role.name)
roles_left.append(dep_role) roles_left.append(dep_role)
else: else:
self.display.display('- dependency %s already pending installation.' % dep_role.name) display.display('- dependency %s already pending installation.' % dep_role.name)
else: else:
self.display.display('- dependency %s is already installed, skipping.' % dep_role.name) display.display('- dependency %s is already installed, skipping.' % dep_role.name)
if not installed: if not installed:
self.display.warning("- %s was NOT installed successfully." % role.name) display.warning("- %s was NOT installed successfully." % role.name)
self.exit_without_ignore() self.exit_without_ignore()
return 0 return 0
@ -398,9 +403,9 @@ class GalaxyCLI(CLI):
role = GalaxyRole(self.galaxy, role_name) role = GalaxyRole(self.galaxy, role_name)
try: try:
if role.remove(): if role.remove():
self.display.display('- successfully removed %s' % role_name) display.display('- successfully removed %s' % role_name)
else: else:
self.display.display('- %s is not installed, skipping.' % role_name) display.display('- %s is not installed, skipping.' % role_name)
except Exception as e: except Exception as e:
raise AnsibleError("Failed to remove role %s: %s" % (role_name, str(e))) raise AnsibleError("Failed to remove role %s: %s" % (role_name, str(e)))
@ -429,9 +434,9 @@ class GalaxyCLI(CLI):
if not version: if not version:
version = "(unknown version)" version = "(unknown version)"
# show some more info about single roles here # show some more info about single roles here
self.display.display("- %s, %s" % (name, version)) display.display("- %s, %s" % (name, version))
else: else:
self.display.display("- the role %s was not found" % name) display.display("- the role %s was not found" % name)
else: else:
# show all valid roles in the roles_path directory # show all valid roles in the roles_path directory
roles_path = self.get_opt('roles_path') roles_path = self.get_opt('roles_path')
@ -450,7 +455,7 @@ class GalaxyCLI(CLI):
version = install_info.get("version", None) version = install_info.get("version", None)
if not version: if not version:
version = "(unknown version)" version = "(unknown version)"
self.display.display("- %s, %s" % (path_file, version)) display.display("- %s, %s" % (path_file, version))
return 0 return 0
def execute_search(self): def execute_search(self):
@ -464,7 +469,7 @@ class GalaxyCLI(CLI):
response = self.api.search_roles(search, self.options.platforms, self.options.tags) response = self.api.search_roles(search, self.options.platforms, self.options.tags)
if 'count' in response: if 'count' in response:
self.galaxy.display.display("Found %d roles matching your search:\n" % response['count']) display.display("Found %d roles matching your search:\n" % response['count'])
data = '' data = ''
if 'results' in response: if 'results' in response:

View file

@ -33,6 +33,14 @@ from ansible.parsing.dataloader import DataLoader
from ansible.utils.vars import load_extra_vars from ansible.utils.vars import load_extra_vars
from ansible.vars import VariableManager from ansible.vars import VariableManager
try:
from __main__ import display
display = display
except ImportError:
from ansible.utils.display import Display
display = Display()
#--------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------
class PlaybookCLI(CLI): class PlaybookCLI(CLI):
@ -73,7 +81,7 @@ class PlaybookCLI(CLI):
if len(self.args) == 0: if len(self.args) == 0:
raise AnsibleOptionsError("You must specify a playbook file to run") raise AnsibleOptionsError("You must specify a playbook file to run")
self.display.verbosity = self.options.verbosity display.verbosity = self.options.verbosity
self.validate_conflicts(runas_opts=True, vault_opts=True, fork_opts=True) self.validate_conflicts(runas_opts=True, vault_opts=True, fork_opts=True)
def run(self): def run(self):
@ -129,7 +137,7 @@ class PlaybookCLI(CLI):
no_hosts = False no_hosts = False
if len(inventory.list_hosts()) == 0: if len(inventory.list_hosts()) == 0:
# Empty inventory # Empty inventory
self.display.warning("provided hosts list is empty, only localhost is available") display.warning("provided hosts list is empty, only localhost is available")
no_hosts = True no_hosts = True
inventory.subset(self.options.subset) inventory.subset(self.options.subset)
if len(inventory.list_hosts()) == 0 and no_hosts is False: if len(inventory.list_hosts()) == 0 and no_hosts is False:
@ -137,14 +145,14 @@ class PlaybookCLI(CLI):
raise AnsibleError("Specified --limit does not match any hosts") raise AnsibleError("Specified --limit does not match any hosts")
# create the playbook executor, which manages running the plays via a task queue manager # create the playbook executor, which manages running the plays via a task queue manager
pbex = PlaybookExecutor(playbooks=self.args, inventory=inventory, variable_manager=variable_manager, loader=loader, display=self.display, options=self.options, passwords=passwords) pbex = PlaybookExecutor(playbooks=self.args, inventory=inventory, variable_manager=variable_manager, loader=loader, options=self.options, passwords=passwords)
results = pbex.run() results = pbex.run()
if isinstance(results, list): if isinstance(results, list):
for p in results: for p in results:
self.display.display('\nplaybook: %s' % p['playbook']) display.display('\nplaybook: %s' % p['playbook'])
i = 1 i = 1
for play in p['plays']: for play in p['plays']:
if play.name: if play.name:
@ -164,7 +172,7 @@ class PlaybookCLI(CLI):
for host in playhosts: for host in playhosts:
msg += "\n %s" % host msg += "\n %s" % host
self.display.display(msg) display.display(msg)
if self.options.listtags or self.options.listtasks: if self.options.listtags or self.options.listtasks:
taskmsg = ' tasks:' taskmsg = ' tasks:'
@ -180,7 +188,7 @@ class PlaybookCLI(CLI):
taskmsg += " TAGS: [%s]" % ','.join(mytags.union(set(task.tags))) taskmsg += " TAGS: [%s]" % ','.join(mytags.union(set(task.tags)))
j = j + 1 j = j + 1
self.display.display(taskmsg) display.display(taskmsg)
i = i + 1 i = i + 1
return 0 return 0

View file

@ -33,6 +33,14 @@ from ansible.cli import CLI
from ansible.plugins import module_loader from ansible.plugins import module_loader
from ansible.utils.cmd_functions import run_cmd from ansible.utils.cmd_functions import run_cmd
try:
from __main__ import display
display = display
except ImportError:
from ansible.utils.display import Display
display = Display()
######################################################## ########################################################
class PullCLI(CLI): class PullCLI(CLI):
@ -94,7 +102,7 @@ class PullCLI(CLI):
if self.options.module_name not in self.SUPPORTED_REPO_MODULES: if self.options.module_name not in self.SUPPORTED_REPO_MODULES:
raise AnsibleOptionsError("Unsuported repo module %s, choices are %s" % (self.options.module_name, ','.join(self.SUPPORTED_REPO_MODULES))) raise AnsibleOptionsError("Unsuported repo module %s, choices are %s" % (self.options.module_name, ','.join(self.SUPPORTED_REPO_MODULES)))
self.display.verbosity = self.options.verbosity display.verbosity = self.options.verbosity
self.validate_conflicts(vault_opts=True) self.validate_conflicts(vault_opts=True)
def run(self): def run(self):
@ -104,8 +112,8 @@ class PullCLI(CLI):
# log command line # log command line
now = datetime.datetime.now() now = datetime.datetime.now()
self.display.display(now.strftime("Starting Ansible Pull at %F %T")) display.display(now.strftime("Starting Ansible Pull at %F %T"))
self.display.display(' '.join(sys.argv)) display.display(' '.join(sys.argv))
# Build Checkout command # Build Checkout command
# Now construct the ansible command # Now construct the ansible command
@ -152,19 +160,19 @@ class PullCLI(CLI):
# Nap? # Nap?
if self.options.sleep: if self.options.sleep:
self.display.display("Sleeping for %d seconds..." % self.options.sleep) display.display("Sleeping for %d seconds..." % self.options.sleep)
time.sleep(self.options.sleep); time.sleep(self.options.sleep)
# RUN the Checkout command # RUN the Checkout command
rc, out, err = run_cmd(cmd, live=True) rc, out, err = run_cmd(cmd, live=True)
if rc != 0: if rc != 0:
if self.options.force: if self.options.force:
self.display.warning("Unable to update repository. Continuing with (forced) run of playbook.") display.warning("Unable to update repository. Continuing with (forced) run of playbook.")
else: else:
return rc return rc
elif self.options.ifchanged and '"changed": true' not in out: elif self.options.ifchanged and '"changed": true' not in out:
self.display.display("Repository has not changed, quitting.") display.display("Repository has not changed, quitting.")
return 0 return 0
playbook = self.select_playbook(path) playbook = self.select_playbook(path)
@ -197,11 +205,10 @@ class PullCLI(CLI):
try: try:
shutil.rmtree(self.options.dest) shutil.rmtree(self.options.dest)
except Exception as e: except Exception as e:
self.display.error("Failed to remove %s: %s" % (self.options.dest, str(e))) display.error("Failed to remove %s: %s" % (self.options.dest, str(e)))
return rc return rc
def try_playbook(self, path): def try_playbook(self, path):
if not os.path.exists(path): if not os.path.exists(path):
return 1 return 1
@ -215,7 +222,7 @@ class PullCLI(CLI):
playbook = os.path.join(path, self.args[0]) playbook = os.path.join(path, self.args[0])
rc = self.try_playbook(playbook) rc = self.try_playbook(playbook)
if rc != 0: if rc != 0:
self.display.warning("%s: %s" % (playbook, self.PLAYBOOK_ERRORS[rc])) display.warning("%s: %s" % (playbook, self.PLAYBOOK_ERRORS[rc]))
return None return None
return playbook return playbook
else: else:
@ -232,5 +239,5 @@ class PullCLI(CLI):
else: else:
errors.append("%s: %s" % (pb, self.PLAYBOOK_ERRORS[rc])) errors.append("%s: %s" % (pb, self.PLAYBOOK_ERRORS[rc]))
if playbook is None: if playbook is None:
self.display.warning("\n".join(errors)) display.warning("\n".join(errors))
return playbook return playbook

View file

@ -27,16 +27,24 @@ from ansible.parsing.dataloader import DataLoader
from ansible.parsing.vault import VaultEditor from ansible.parsing.vault import VaultEditor
from ansible.cli import CLI from ansible.cli import CLI
try:
from __main__ import display
display = display
except ImportError:
from ansible.utils.display import Display
display = Display()
class VaultCLI(CLI): class VaultCLI(CLI):
""" Vault command line class """ """ Vault command line class """
VALID_ACTIONS = ("create", "decrypt", "edit", "encrypt", "rekey", "view") VALID_ACTIONS = ("create", "decrypt", "edit", "encrypt", "rekey", "view")
def __init__(self, args, display=None): def __init__(self, args):
self.vault_pass = None self.vault_pass = None
self.new_vault_pass = None self.new_vault_pass = None
super(VaultCLI, self).__init__(args, display) super(VaultCLI, self).__init__(args)
def parse(self): def parse(self):
@ -63,7 +71,7 @@ class VaultCLI(CLI):
self.parser.set_usage("usage: %prog rekey [options] file_name") self.parser.set_usage("usage: %prog rekey [options] file_name")
self.options, self.args = self.parser.parse_args() self.options, self.args = self.parser.parse_args()
self.display.verbosity = self.options.verbosity display.verbosity = self.options.verbosity
can_output = ['encrypt', 'decrypt'] can_output = ['encrypt', 'decrypt']
@ -117,24 +125,24 @@ class VaultCLI(CLI):
def execute_encrypt(self): def execute_encrypt(self):
if len(self.args) == 0 and sys.stdin.isatty(): if len(self.args) == 0 and sys.stdin.isatty():
self.display.display("Reading plaintext input from stdin", stderr=True) display.display("Reading plaintext input from stdin", stderr=True)
for f in self.args or ['-']: for f in self.args or ['-']:
self.editor.encrypt_file(f, output_file=self.options.output_file) self.editor.encrypt_file(f, output_file=self.options.output_file)
if sys.stdout.isatty(): if sys.stdout.isatty():
self.display.display("Encryption successful", stderr=True) display.display("Encryption successful", stderr=True)
def execute_decrypt(self): def execute_decrypt(self):
if len(self.args) == 0 and sys.stdin.isatty(): if len(self.args) == 0 and sys.stdin.isatty():
self.display.display("Reading ciphertext input from stdin", stderr=True) display.display("Reading ciphertext input from stdin", stderr=True)
for f in self.args or ['-']: for f in self.args or ['-']:
self.editor.decrypt_file(f, output_file=self.options.output_file) self.editor.decrypt_file(f, output_file=self.options.output_file)
if sys.stdout.isatty(): if sys.stdout.isatty():
self.display.display("Decryption successful", stderr=True) display.display("Decryption successful", stderr=True)
def execute_create(self): def execute_create(self):
@ -160,4 +168,4 @@ class VaultCLI(CLI):
for f in self.args: for f in self.args:
self.editor.rekey_file(f, self.new_vault_pass) self.editor.rekey_file(f, self.new_vault_pass)
self.display.display("Rekey successful", stderr=True) display.display("Rekey successful", stderr=True)

View file

@ -27,7 +27,6 @@ import sys
from ansible.compat.six import string_types from ansible.compat.six import string_types
from ansible import constants as C
from ansible.executor.task_queue_manager import TaskQueueManager from ansible.executor.task_queue_manager import TaskQueueManager
from ansible.playbook import Playbook from ansible.playbook import Playbook
from ansible.template import Templar from ansible.template import Templar
@ -36,6 +35,14 @@ from ansible.utils.color import colorize, hostcolor
from ansible.utils.encrypt import do_encrypt from ansible.utils.encrypt import do_encrypt
from ansible.utils.unicode import to_unicode from ansible.utils.unicode import to_unicode
try:
from __main__ import display
display = display
except ImportError:
from ansible.utils.display import Display
display = Display()
class PlaybookExecutor: class PlaybookExecutor:
''' '''
@ -43,12 +50,11 @@ class PlaybookExecutor:
basis for bin/ansible-playbook operation. basis for bin/ansible-playbook operation.
''' '''
def __init__(self, playbooks, inventory, variable_manager, loader, display, options, passwords): def __init__(self, playbooks, inventory, variable_manager, loader, options, passwords):
self._playbooks = playbooks self._playbooks = playbooks
self._inventory = inventory self._inventory = inventory
self._variable_manager = variable_manager self._variable_manager = variable_manager
self._loader = loader self._loader = loader
self._display = display
self._options = options self._options = options
self.passwords = passwords self.passwords = passwords
self._unreachable_hosts = dict() self._unreachable_hosts = dict()
@ -56,7 +62,7 @@ class PlaybookExecutor:
if options.listhosts or options.listtasks or options.listtags or options.syntax: if options.listhosts or options.listtasks or options.listtags or options.syntax:
self._tqm = None self._tqm = None
else: else:
self._tqm = TaskQueueManager(inventory=inventory, variable_manager=variable_manager, loader=loader, display=display, options=options, passwords=self.passwords) self._tqm = TaskQueueManager(inventory=inventory, variable_manager=variable_manager, loader=loader, options=options, passwords=self.passwords)
def run(self): def run(self):
@ -81,7 +87,7 @@ class PlaybookExecutor:
i = 1 i = 1
plays = pb.get_plays() plays = pb.get_plays()
self._display.vv('%d plays in %s' % (len(plays), playbook_path)) display.vv('%d plays in %s' % (len(plays), playbook_path))
for play in plays: for play in plays:
if play._included_path is not None: if play._included_path is not None:
@ -178,18 +184,18 @@ class PlaybookExecutor:
self._cleanup() self._cleanup()
if self._options.syntax: if self._options.syntax:
self.display.display("No issues encountered") display.display("No issues encountered")
return result return result
# TODO: this stat summary stuff should be cleaned up and moved # TODO: this stat summary stuff should be cleaned up and moved
# to a new method, if it even belongs here... # to a new method, if it even belongs here...
self._display.banner("PLAY RECAP") display.banner("PLAY RECAP")
hosts = sorted(self._tqm._stats.processed.keys()) hosts = sorted(self._tqm._stats.processed.keys())
for h in hosts: for h in hosts:
t = self._tqm._stats.summarize(h) t = self._tqm._stats.summarize(h)
self._display.display(u"%s : %s %s %s %s" % ( display.display(u"%s : %s %s %s %s" % (
hostcolor(h, t), hostcolor(h, t),
colorize(u'ok', t['ok'], 'green'), colorize(u'ok', t['ok'], 'green'),
colorize(u'changed', t['changed'], 'yellow'), colorize(u'changed', t['changed'], 'yellow'),
@ -198,7 +204,7 @@ class PlaybookExecutor:
screen_only=True screen_only=True
) )
self._display.display(u"%s : %s %s %s %s" % ( display.display(u"%s : %s %s %s %s" % (
hostcolor(h, t, False), hostcolor(h, t, False),
colorize(u'ok', t['ok'], None), colorize(u'ok', t['ok'], None),
colorize(u'changed', t['changed'], None), colorize(u'changed', t['changed'], None),
@ -207,7 +213,7 @@ class PlaybookExecutor:
log_only=True log_only=True
) )
self._display.display("", screen_only=True) display.display("", screen_only=True)
# END STATS STUFF # END STATS STUFF
return result return result
@ -230,7 +236,7 @@ class PlaybookExecutor:
serial_pct = int(play.serial.replace("%","")) serial_pct = int(play.serial.replace("%",""))
serial = int((serial_pct/100.0) * len(all_hosts)) serial = int((serial_pct/100.0) * len(all_hosts))
else: else:
if play.serial is None: if play.serial is None:
serial = -1 serial = -1
else: else:
serial = int(play.serial) serial = int(play.serial)
@ -281,12 +287,12 @@ class PlaybookExecutor:
second = do_prompt("confirm " + msg, private) second = do_prompt("confirm " + msg, private)
if result == second: if result == second:
break break
self._display.display("***** VALUES ENTERED DO NOT MATCH ****") display.display("***** VALUES ENTERED DO NOT MATCH ****")
else: else:
result = do_prompt(msg, private) result = do_prompt(msg, private)
else: else:
result = None result = None
self._display.warning("Not prompting as we are not in interactive mode") display.warning("Not prompting as we are not in interactive mode")
# if result is false and default is not None # if result is false and default is not None
if not result and default is not None: if not result and default is not None:
@ -298,5 +304,3 @@ class PlaybookExecutor:
# handle utf-8 chars # handle utf-8 chars
result = to_unicode(result, errors='strict') result = to_unicode(result, errors='strict')
return result return result

View file

@ -21,7 +21,6 @@ __metaclass__ = type
import multiprocessing import multiprocessing
import os import os
import socket
import tempfile import tempfile
from ansible import constants as C from ansible import constants as C
@ -34,8 +33,16 @@ from ansible.playbook.play_context import PlayContext
from ansible.plugins import callback_loader, strategy_loader, module_loader from ansible.plugins import callback_loader, strategy_loader, module_loader
from ansible.template import Templar from ansible.template import Templar
try:
from __main__ import display
display = display
except ImportError:
from ansible.utils.display import Display
display = Display()
__all__ = ['TaskQueueManager'] __all__ = ['TaskQueueManager']
class TaskQueueManager: class TaskQueueManager:
''' '''
@ -48,12 +55,11 @@ class TaskQueueManager:
which dispatches the Play's tasks to hosts. which dispatches the Play's tasks to hosts.
''' '''
def __init__(self, inventory, variable_manager, loader, display, options, passwords, stdout_callback=None): def __init__(self, inventory, variable_manager, loader, options, passwords, stdout_callback=None):
self._inventory = inventory self._inventory = inventory
self._variable_manager = variable_manager self._variable_manager = variable_manager
self._loader = loader self._loader = loader
self._display = display
self._options = options self._options = options
self._stats = AggregateStats() self._stats = AggregateStats()
self.passwords = passwords self.passwords = passwords
@ -155,7 +161,9 @@ class TaskQueueManager:
elif callback_needs_whitelist and (C.DEFAULT_CALLBACK_WHITELIST is None or callback_name not in C.DEFAULT_CALLBACK_WHITELIST): elif callback_needs_whitelist and (C.DEFAULT_CALLBACK_WHITELIST is None or callback_name not in C.DEFAULT_CALLBACK_WHITELIST):
continue continue
self._callback_plugins.append(callback_plugin(self._display)) # is it too late to change the API for v2 callback plugins?
# display is not necessary.
self._callback_plugins.append(callback_plugin(display))
else: else:
self._callback_plugins.append(callback_plugin()) self._callback_plugins.append(callback_plugin())
@ -221,7 +229,7 @@ class TaskQueueManager:
return play_return return play_return
def cleanup(self): def cleanup(self):
self._display.debug("RUNNING CLEANUP") display.debug("RUNNING CLEANUP")
self.terminate() self.terminate()
self._final_q.close() self._final_q.close()
self._cleanup_processes() self._cleanup_processes()
@ -275,4 +283,4 @@ class TaskQueueManager:
v1_method = method.replace('v2_','') v1_method = method.replace('v2_','')
v1_method(*args, **kwargs) v1_method(*args, **kwargs)
except Exception: except Exception:
self._display.warning('Error when using %s: %s' % (method, str(e))) display.warning('Error when using %s: %s' % (method, str(e)))

View file

@ -28,7 +28,6 @@ import os
from ansible.compat.six import string_types from ansible.compat.six import string_types
from ansible.errors import AnsibleError from ansible.errors import AnsibleError
from ansible.utils.display import Display
# default_readme_template # default_readme_template
# default_meta_template # default_meta_template
@ -37,12 +36,7 @@ from ansible.utils.display import Display
class Galaxy(object): class Galaxy(object):
''' Keeps global galaxy info ''' ''' Keeps global galaxy info '''
def __init__(self, options, display=None): def __init__(self, options):
if display is None:
self.display = Display()
else:
self.display = display
self.options = options self.options = options
roles_paths = getattr(self.options, 'roles_path', []) roles_paths = getattr(self.options, 'roles_path', [])
@ -65,11 +59,9 @@ class Galaxy(object):
def remove_role(self, role_name): def remove_role(self, role_name):
del self.roles[role_name] del self.roles[role_name]
def _str_from_data_file(self, filename): def _str_from_data_file(self, filename):
myfile = os.path.join(self.DATA_PATH, filename) myfile = os.path.join(self.DATA_PATH, filename)
try: try:
return open(myfile).read() return open(myfile).read()
except Exception as e: except Exception as e:
raise AnsibleError("Could not open %s: %s" % (filename, str(e))) raise AnsibleError("Could not open %s: %s" % (filename, str(e)))

View file

@ -61,7 +61,6 @@ class TestPlayIterator(unittest.TestCase):
}) })
mock_inventory = MagicMock() mock_inventory = MagicMock()
mock_display = MagicMock()
mock_var_manager = MagicMock() mock_var_manager = MagicMock()
# fake out options to use the syntax CLI switch, which will ensure # fake out options to use the syntax CLI switch, which will ensure
@ -74,7 +73,6 @@ class TestPlayIterator(unittest.TestCase):
inventory=mock_inventory, inventory=mock_inventory,
variable_manager=mock_var_manager, variable_manager=mock_var_manager,
loader=fake_loader, loader=fake_loader,
display=mock_display,
options=mock_options, options=mock_options,
passwords=[], passwords=[],
) )