Split up the base_parser function
The goal of breaking apart the base_parser() function is to get rid of a bunch of conditionals and parameters in the code and, instead, make code look like simple composition. When splitting, a choice had to be made as to whether this would operate by side effect (modifying a passed in parser) or side effect-free (returning a new parser everytime). Making a version that's side-effect-free appears to be fighting with the optparse API (it wants to work by creating a parser object, configuring the object, and then parsing the arguments with it) so instead, make it clear that our helper functions are modifying the passed in parser by (1) not returning the parser and (2) changing the function names to be more clear that it is operating by side-effect. Also move all of the generic optparse code, along with the argument context classes, into a new subdirectory.
This commit is contained in:
parent
afdbb0d9d5
commit
7e92ff823e
21 changed files with 545 additions and 486 deletions
5
lib/ansible/arguments/__init__.py
Normal file
5
lib/ansible/arguments/__init__.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# Copyright: (c) 2018, Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
|
@ -61,7 +61,16 @@ class _ABCSingleton(Singleton, ABCMeta):
|
||||||
|
|
||||||
|
|
||||||
class CLIArgs(ImmutableDict):
|
class CLIArgs(ImmutableDict):
|
||||||
"""Hold a parsed copy of cli arguments"""
|
"""
|
||||||
|
Hold a parsed copy of cli arguments
|
||||||
|
|
||||||
|
We have both this non-Singleton version and the Singleton, GlobalCLIArgs, version to leave us
|
||||||
|
room to implement a Context object in the future. Whereas there should only be one set of args
|
||||||
|
in a global context, individual Context objects might want to pretend that they have different
|
||||||
|
command line switches to trigger different behaviour when they run. So if we support Contexts
|
||||||
|
in the future, they would use CLIArgs instead of GlobalCLIArgs to store their version of command
|
||||||
|
line flags.
|
||||||
|
"""
|
||||||
def __init__(self, mapping):
|
def __init__(self, mapping):
|
||||||
toplevel = {}
|
toplevel = {}
|
||||||
for key, value in mapping.items():
|
for key, value in mapping.items():
|
377
lib/ansible/arguments/optparse_helpers.py
Normal file
377
lib/ansible/arguments/optparse_helpers.py
Normal file
|
@ -0,0 +1,377 @@
|
||||||
|
# Copyright: (c) 2018, Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import operator
|
||||||
|
import optparse
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
import ansible
|
||||||
|
from ansible import constants as C
|
||||||
|
from ansible.module_utils.six import string_types
|
||||||
|
from ansible.release import __version__
|
||||||
|
from ansible.utils.path import unfrackpath
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Special purpose OptionParsers
|
||||||
|
#
|
||||||
|
|
||||||
|
class SortedOptParser(optparse.OptionParser):
|
||||||
|
"""Optparser which sorts the options by opt before outputting --help"""
|
||||||
|
|
||||||
|
def format_help(self, formatter=None, epilog=None):
|
||||||
|
self.option_list.sort(key=operator.methodcaller('get_opt_string'))
|
||||||
|
return optparse.OptionParser.format_help(self, formatter=None)
|
||||||
|
|
||||||
|
|
||||||
|
# Note: Inherit from SortedOptParser so that we get our format_help method
|
||||||
|
class InvalidOptsParser(SortedOptParser):
|
||||||
|
"""Ignore invalid options.
|
||||||
|
|
||||||
|
Meant for the special case where we need to take care of help and version but may not know the
|
||||||
|
full range of options yet.
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
See it in use in ansible.cli.CLI.set_action
|
||||||
|
"""
|
||||||
|
def __init__(self, parser):
|
||||||
|
# Since this is special purposed to just handle help and version, we
|
||||||
|
# take a pre-existing option parser here and set our options from
|
||||||
|
# that. This allows us to give accurate help based on the given
|
||||||
|
# option parser.
|
||||||
|
SortedOptParser.__init__(self, usage=parser.usage,
|
||||||
|
option_list=parser.option_list,
|
||||||
|
option_class=parser.option_class,
|
||||||
|
conflict_handler=parser.conflict_handler,
|
||||||
|
description=parser.description,
|
||||||
|
formatter=parser.formatter,
|
||||||
|
add_help_option=False,
|
||||||
|
prog=parser.prog,
|
||||||
|
epilog=parser.epilog)
|
||||||
|
self.version = parser.version
|
||||||
|
|
||||||
|
def _process_long_opt(self, rargs, values):
|
||||||
|
try:
|
||||||
|
optparse.OptionParser._process_long_opt(self, rargs, values)
|
||||||
|
except optparse.BadOptionError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _process_short_opts(self, rargs, values):
|
||||||
|
try:
|
||||||
|
optparse.OptionParser._process_short_opts(self, rargs, values)
|
||||||
|
except optparse.BadOptionError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Callbacks to validate and normalize Options
|
||||||
|
#
|
||||||
|
|
||||||
|
def unfrack_paths(option, opt, value, parser):
|
||||||
|
"""Turn an Option's value into a list of paths in Ansible locations"""
|
||||||
|
paths = getattr(parser.values, option.dest)
|
||||||
|
if paths is None:
|
||||||
|
paths = []
|
||||||
|
|
||||||
|
if isinstance(value, string_types):
|
||||||
|
paths[:0] = [unfrackpath(x) for x in value.split(os.pathsep) if x]
|
||||||
|
elif isinstance(value, list):
|
||||||
|
paths[:0] = [unfrackpath(x) for x in value if x]
|
||||||
|
else:
|
||||||
|
pass # FIXME: should we raise options error?
|
||||||
|
|
||||||
|
setattr(parser.values, option.dest, paths)
|
||||||
|
|
||||||
|
|
||||||
|
def unfrack_path(option, opt, value, parser):
|
||||||
|
"""Turn an Option's data into a single path in Ansible locations"""
|
||||||
|
if value != '-':
|
||||||
|
setattr(parser.values, option.dest, unfrackpath(value))
|
||||||
|
else:
|
||||||
|
setattr(parser.values, option.dest, value)
|
||||||
|
|
||||||
|
|
||||||
|
def _git_repo_info(repo_path):
|
||||||
|
""" returns a string containing git branch, commit id and commit date """
|
||||||
|
result = None
|
||||||
|
if os.path.exists(repo_path):
|
||||||
|
# Check if the .git is a file. If it is a file, it means that we are in a submodule structure.
|
||||||
|
if os.path.isfile(repo_path):
|
||||||
|
try:
|
||||||
|
gitdir = yaml.safe_load(open(repo_path)).get('gitdir')
|
||||||
|
# There is a possibility the .git file to have an absolute path.
|
||||||
|
if os.path.isabs(gitdir):
|
||||||
|
repo_path = gitdir
|
||||||
|
else:
|
||||||
|
repo_path = os.path.join(repo_path[:-4], gitdir)
|
||||||
|
except (IOError, AttributeError):
|
||||||
|
return ''
|
||||||
|
with open(os.path.join(repo_path, "HEAD")) as f:
|
||||||
|
line = f.readline().rstrip("\n")
|
||||||
|
if line.startswith("ref:"):
|
||||||
|
branch_path = os.path.join(repo_path, line[5:])
|
||||||
|
else:
|
||||||
|
branch_path = None
|
||||||
|
if branch_path and os.path.exists(branch_path):
|
||||||
|
branch = '/'.join(line.split('/')[2:])
|
||||||
|
with open(branch_path) as f:
|
||||||
|
commit = f.readline()[:10]
|
||||||
|
else:
|
||||||
|
# detached HEAD
|
||||||
|
commit = line[:10]
|
||||||
|
branch = 'detached HEAD'
|
||||||
|
branch_path = os.path.join(repo_path, "HEAD")
|
||||||
|
|
||||||
|
date = time.localtime(os.stat(branch_path).st_mtime)
|
||||||
|
if time.daylight == 0:
|
||||||
|
offset = time.timezone
|
||||||
|
else:
|
||||||
|
offset = time.altzone
|
||||||
|
result = "({0} {1}) last updated {2} (GMT {3:+04d})".format(branch, commit, time.strftime("%Y/%m/%d %H:%M:%S", date), int(offset / -36))
|
||||||
|
else:
|
||||||
|
result = ''
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _gitinfo():
|
||||||
|
basedir = os.path.join(os.path.dirname(__file__), '..', '..', '..')
|
||||||
|
repo_path = os.path.join(basedir, '.git')
|
||||||
|
result = _git_repo_info(repo_path)
|
||||||
|
submodules = os.path.join(basedir, '.gitmodules')
|
||||||
|
|
||||||
|
if not os.path.exists(submodules):
|
||||||
|
return result
|
||||||
|
|
||||||
|
with open(submodules) as f:
|
||||||
|
for line in f:
|
||||||
|
tokens = line.strip().split(' ')
|
||||||
|
if tokens[0] == 'path':
|
||||||
|
submodule_path = tokens[2]
|
||||||
|
submodule_info = _git_repo_info(os.path.join(basedir, submodule_path, '.git'))
|
||||||
|
if not submodule_info:
|
||||||
|
submodule_info = ' not found - use git submodule update --init ' + submodule_path
|
||||||
|
result += "\n {0}: {1}".format(submodule_path, submodule_info)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def version(prog=None):
|
||||||
|
""" return ansible version """
|
||||||
|
if prog:
|
||||||
|
result = " ".join((prog, __version__))
|
||||||
|
else:
|
||||||
|
result = __version__
|
||||||
|
|
||||||
|
gitinfo = _gitinfo()
|
||||||
|
if gitinfo:
|
||||||
|
result = result + " {0}".format(gitinfo)
|
||||||
|
result += "\n config file = %s" % C.CONFIG_FILE
|
||||||
|
if C.DEFAULT_MODULE_PATH is None:
|
||||||
|
cpath = "Default w/o overrides"
|
||||||
|
else:
|
||||||
|
cpath = C.DEFAULT_MODULE_PATH
|
||||||
|
result = result + "\n configured module search path = %s" % cpath
|
||||||
|
result = result + "\n ansible python module location = %s" % ':'.join(ansible.__path__)
|
||||||
|
result = result + "\n executable location = %s" % sys.argv[0]
|
||||||
|
result = result + "\n python version = %s" % ''.join(sys.version.splitlines())
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Functions to add pre-canned options to an OptionParser
|
||||||
|
#
|
||||||
|
|
||||||
|
def create_base_parser(usage="", desc=None, epilog=None):
|
||||||
|
"""
|
||||||
|
Create an options parser for all ansible scripts
|
||||||
|
"""
|
||||||
|
# base opts
|
||||||
|
parser = SortedOptParser(usage, version=version("%prog"), description=desc, epilog=epilog)
|
||||||
|
parser.remove_option('--version')
|
||||||
|
version_help = "show program's version number, config file location, configured module search path," \
|
||||||
|
" module location, executable location and exit"
|
||||||
|
parser.add_option('--version', action="version", help=version_help)
|
||||||
|
parser.add_option('-v', '--verbose', dest='verbosity', default=C.DEFAULT_VERBOSITY, action="count",
|
||||||
|
help="verbose mode (-vvv for more, -vvvv to enable connection debugging)")
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def add_async_options(parser):
|
||||||
|
"""Add options for commands which can launch async tasks"""
|
||||||
|
parser.add_option('-P', '--poll', default=C.DEFAULT_POLL_INTERVAL, type='int', dest='poll_interval',
|
||||||
|
help="set the poll interval if using -B (default=%s)" % C.DEFAULT_POLL_INTERVAL)
|
||||||
|
parser.add_option('-B', '--background', dest='seconds', type='int', default=0,
|
||||||
|
help='run asynchronously, failing after X seconds (default=N/A)')
|
||||||
|
|
||||||
|
|
||||||
|
def add_basedir_options(parser):
|
||||||
|
"""Add options for commands which can set a playbook basedir"""
|
||||||
|
parser.add_option('--playbook-dir', default=None, dest='basedir', action='store',
|
||||||
|
help="Since this tool does not use playbooks, use this as a subsitute playbook directory."
|
||||||
|
"This sets the relative path for many features including roles/ group_vars/ etc.")
|
||||||
|
|
||||||
|
|
||||||
|
def add_check_options(parser):
|
||||||
|
"""Add options for commands which can run with diagnostic information of tasks"""
|
||||||
|
parser.add_option("-C", "--check", default=False, dest='check', action='store_true',
|
||||||
|
help="don't make any changes; instead, try to predict some of the changes that may occur")
|
||||||
|
parser.add_option('--syntax-check', dest='syntax', action='store_true',
|
||||||
|
help="perform a syntax check on the playbook, but do not execute it")
|
||||||
|
parser.add_option("-D", "--diff", default=C.DIFF_ALWAYS, dest='diff', action='store_true',
|
||||||
|
help="when changing (small) files and templates, show the differences in those"
|
||||||
|
" files; works great with --check")
|
||||||
|
|
||||||
|
|
||||||
|
def add_connect_options(parser):
|
||||||
|
"""Add options for commands which need to connection to other hosts"""
|
||||||
|
connect_group = optparse.OptionGroup(parser, "Connection Options", "control as whom and how to connect to hosts")
|
||||||
|
|
||||||
|
connect_group.add_option('-k', '--ask-pass', default=C.DEFAULT_ASK_PASS, dest='ask_pass', action='store_true',
|
||||||
|
help='ask for connection password')
|
||||||
|
connect_group.add_option('--private-key', '--key-file', default=C.DEFAULT_PRIVATE_KEY_FILE, dest='private_key_file',
|
||||||
|
help='use this file to authenticate the connection', action="callback", callback=unfrack_path, type='string')
|
||||||
|
connect_group.add_option('-u', '--user', default=C.DEFAULT_REMOTE_USER, dest='remote_user',
|
||||||
|
help='connect as this user (default=%s)' % C.DEFAULT_REMOTE_USER)
|
||||||
|
connect_group.add_option('-c', '--connection', dest='connection', default=C.DEFAULT_TRANSPORT,
|
||||||
|
help="connection type to use (default=%s)" % C.DEFAULT_TRANSPORT)
|
||||||
|
connect_group.add_option('-T', '--timeout', default=C.DEFAULT_TIMEOUT, type='int', dest='timeout',
|
||||||
|
help="override the connection timeout in seconds (default=%s)" % C.DEFAULT_TIMEOUT)
|
||||||
|
connect_group.add_option('--ssh-common-args', default='', dest='ssh_common_args',
|
||||||
|
help="specify common arguments to pass to sftp/scp/ssh (e.g. ProxyCommand)")
|
||||||
|
connect_group.add_option('--sftp-extra-args', default='', dest='sftp_extra_args',
|
||||||
|
help="specify extra arguments to pass to sftp only (e.g. -f, -l)")
|
||||||
|
connect_group.add_option('--scp-extra-args', default='', dest='scp_extra_args',
|
||||||
|
help="specify extra arguments to pass to scp only (e.g. -l)")
|
||||||
|
connect_group.add_option('--ssh-extra-args', default='', dest='ssh_extra_args',
|
||||||
|
help="specify extra arguments to pass to ssh only (e.g. -R)")
|
||||||
|
|
||||||
|
parser.add_option_group(connect_group)
|
||||||
|
|
||||||
|
|
||||||
|
def add_fork_options(parser):
|
||||||
|
"""Add options for commands that can fork worker processes"""
|
||||||
|
parser.add_option('-f', '--forks', dest='forks', default=C.DEFAULT_FORKS, type='int',
|
||||||
|
help="specify number of parallel processes to use (default=%s)" % C.DEFAULT_FORKS)
|
||||||
|
|
||||||
|
|
||||||
|
def add_inventory_options(parser):
|
||||||
|
"""Add options for commands that utilize inventory"""
|
||||||
|
parser.add_option('-i', '--inventory', '--inventory-file', dest='inventory', action="append",
|
||||||
|
help="specify inventory host path or comma separated host list. --inventory-file is deprecated")
|
||||||
|
parser.add_option('--list-hosts', dest='listhosts', action='store_true',
|
||||||
|
help='outputs a list of matching hosts; does not execute anything else')
|
||||||
|
parser.add_option('-l', '--limit', default=C.DEFAULT_SUBSET, dest='subset',
|
||||||
|
help='further limit selected hosts to an additional pattern')
|
||||||
|
|
||||||
|
|
||||||
|
def add_meta_options(parser):
|
||||||
|
"""Add options for commands which can launch meta tasks from the command line"""
|
||||||
|
parser.add_option('--force-handlers', default=C.DEFAULT_FORCE_HANDLERS, dest='force_handlers', action='store_true',
|
||||||
|
help="run handlers even if a task fails")
|
||||||
|
parser.add_option('--flush-cache', dest='flush_cache', action='store_true',
|
||||||
|
help="clear the fact cache for every host in inventory")
|
||||||
|
|
||||||
|
|
||||||
|
def add_module_options(parser):
|
||||||
|
"""Add options for commands that load modules"""
|
||||||
|
parser.add_option('-M', '--module-path', dest='module_path', default=None,
|
||||||
|
help="prepend colon-separated path(s) to module library (default=%s)" % C.DEFAULT_MODULE_PATH,
|
||||||
|
action="callback", callback=unfrack_paths, type='str')
|
||||||
|
|
||||||
|
|
||||||
|
def add_output_options(parser):
|
||||||
|
"""Add options for commands which can change their output"""
|
||||||
|
parser.add_option('-o', '--one-line', dest='one_line', action='store_true',
|
||||||
|
help='condense output')
|
||||||
|
parser.add_option('-t', '--tree', dest='tree', default=None,
|
||||||
|
help='log output to this directory')
|
||||||
|
|
||||||
|
|
||||||
|
def add_runas_options(parser):
|
||||||
|
"""
|
||||||
|
Add options for commands which can run tasks as another user
|
||||||
|
|
||||||
|
Note that this includes the options from add_runas_prompt_options(). Only one of these
|
||||||
|
functions should be used.
|
||||||
|
"""
|
||||||
|
runas_group = optparse.OptionGroup(parser, "Privilege Escalation Options", "control how and which user you become as on target hosts")
|
||||||
|
|
||||||
|
# priv user defaults to root later on to enable detecting when this option was given here
|
||||||
|
runas_group.add_option("-s", "--sudo", default=C.DEFAULT_SUDO, action="store_true", dest='sudo',
|
||||||
|
help="run operations with sudo (nopasswd) (deprecated, use become)")
|
||||||
|
runas_group.add_option('-U', '--sudo-user', dest='sudo_user', default=None,
|
||||||
|
help='desired sudo user (default=root) (deprecated, use become)')
|
||||||
|
runas_group.add_option('-S', '--su', default=C.DEFAULT_SU, action='store_true',
|
||||||
|
help='run operations with su (deprecated, use become)')
|
||||||
|
runas_group.add_option('-R', '--su-user', default=None,
|
||||||
|
help='run operations with su as this user (default=%s) (deprecated, use become)' % C.DEFAULT_SU_USER)
|
||||||
|
|
||||||
|
# consolidated privilege escalation (become)
|
||||||
|
runas_group.add_option("-b", "--become", default=C.DEFAULT_BECOME, action="store_true", dest='become',
|
||||||
|
help="run operations with become (does not imply password prompting)")
|
||||||
|
runas_group.add_option('--become-method', dest='become_method', default=C.DEFAULT_BECOME_METHOD, type='choice', choices=C.BECOME_METHODS,
|
||||||
|
help="privilege escalation method to use (default=%s), valid choices: [ %s ]" %
|
||||||
|
(C.DEFAULT_BECOME_METHOD, ' | '.join(C.BECOME_METHODS)))
|
||||||
|
runas_group.add_option('--become-user', default=None, dest='become_user', type='string',
|
||||||
|
help='run operations as this user (default=%s)' % C.DEFAULT_BECOME_USER)
|
||||||
|
|
||||||
|
add_runas_prompt_options(parser, runas_group=runas_group)
|
||||||
|
|
||||||
|
|
||||||
|
def add_runas_prompt_options(parser, runas_group=None):
|
||||||
|
"""
|
||||||
|
Add options for commands which need to prompt for privilege escalation credentials
|
||||||
|
|
||||||
|
Note that add_runas_options() includes these options already. Only one of the two functions
|
||||||
|
should be used.
|
||||||
|
"""
|
||||||
|
if runas_group is None:
|
||||||
|
runas_group = optparse.OptionGroup(parser, "Privilege Escalation Options",
|
||||||
|
"control how and which user you become as on target hosts")
|
||||||
|
|
||||||
|
runas_group.add_option('--ask-sudo-pass', default=C.DEFAULT_ASK_SUDO_PASS, dest='ask_sudo_pass', action='store_true',
|
||||||
|
help='ask for sudo password (deprecated, use become)')
|
||||||
|
runas_group.add_option('--ask-su-pass', default=C.DEFAULT_ASK_SU_PASS, dest='ask_su_pass', action='store_true',
|
||||||
|
help='ask for su password (deprecated, use become)')
|
||||||
|
runas_group.add_option('-K', '--ask-become-pass', default=False, dest='become_ask_pass', action='store_true',
|
||||||
|
help='ask for privilege escalation password')
|
||||||
|
|
||||||
|
parser.add_option_group(runas_group)
|
||||||
|
|
||||||
|
|
||||||
|
def add_runtask_options(parser):
|
||||||
|
"""Add options for commands that run a task"""
|
||||||
|
parser.add_option('-e', '--extra-vars', dest="extra_vars", action="append",
|
||||||
|
help="set additional variables as key=value or YAML/JSON, if filename prepend with @", default=[])
|
||||||
|
|
||||||
|
|
||||||
|
def add_subset_options(parser):
|
||||||
|
"""Add options for commands which can run a subset of tasks"""
|
||||||
|
parser.add_option('-t', '--tags', dest='tags', default=C.TAGS_RUN, action='append',
|
||||||
|
help="only run plays and tasks tagged with these values")
|
||||||
|
parser.add_option('--skip-tags', dest='skip_tags', default=C.TAGS_SKIP, action='append',
|
||||||
|
help="only run plays and tasks whose tags do not match these values")
|
||||||
|
|
||||||
|
|
||||||
|
def add_vault_options(parser):
|
||||||
|
"""Add options for loading vault files"""
|
||||||
|
parser.add_option('--ask-vault-pass', default=C.DEFAULT_ASK_VAULT_PASS, dest='ask_vault_pass', action='store_true',
|
||||||
|
help='ask for vault password')
|
||||||
|
parser.add_option('--vault-password-file', default=[], dest='vault_password_files',
|
||||||
|
help="vault password file", action="callback", callback=unfrack_paths, type='string')
|
||||||
|
parser.add_option('--vault-id', default=[], dest='vault_ids', action='append', type='string',
|
||||||
|
help='the vault identity to use')
|
||||||
|
|
||||||
|
|
||||||
|
def add_vault_rekey_options(parser):
|
||||||
|
"""Add options for commands which can edit/rekey a vault file"""
|
||||||
|
parser.add_option('--new-vault-password-file', default=None, dest='new_vault_password_file',
|
||||||
|
help="new vault password file for rekey", action="callback", callback=unfrack_path, type='string')
|
||||||
|
parser.add_option('--new-vault-id', default=None, dest='new_vault_id', type='string',
|
||||||
|
help='the new vault identity to use for rekey')
|
|
@ -8,20 +8,17 @@ from __future__ import (absolute_import, division, print_function)
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
import getpass
|
import getpass
|
||||||
import operator
|
|
||||||
import optparse
|
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import os.path
|
||||||
import re
|
import re
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
|
|
||||||
import ansible
|
|
||||||
from ansible import constants as C
|
from ansible import constants as C
|
||||||
from ansible import context
|
from ansible import context
|
||||||
|
from ansible.arguments import optparse_helpers as opt_help
|
||||||
from ansible.errors import AnsibleOptionsError, AnsibleError
|
from ansible.errors import AnsibleOptionsError, AnsibleError
|
||||||
from ansible.inventory.manager import InventoryManager
|
from ansible.inventory.manager import InventoryManager
|
||||||
from ansible.module_utils.six import with_metaclass, string_types
|
from ansible.module_utils.six import with_metaclass, string_types
|
||||||
|
@ -38,200 +35,6 @@ from ansible.parsing.vault import PromptVaultSecret, get_file_vault_secret
|
||||||
display = Display()
|
display = Display()
|
||||||
|
|
||||||
|
|
||||||
class SortedOptParser(optparse.OptionParser):
|
|
||||||
'''Optparser which sorts the options by opt before outputting --help'''
|
|
||||||
|
|
||||||
def format_help(self, formatter=None, epilog=None):
|
|
||||||
self.option_list.sort(key=operator.methodcaller('get_opt_string'))
|
|
||||||
return optparse.OptionParser.format_help(self, formatter=None)
|
|
||||||
|
|
||||||
|
|
||||||
# Note: Inherit from SortedOptParser so that we get our format_help method
|
|
||||||
class InvalidOptsParser(SortedOptParser):
|
|
||||||
'''Ignore invalid options.
|
|
||||||
|
|
||||||
Meant for the special case where we need to take care of help and version
|
|
||||||
but may not know the full range of options yet. (See it in use in set_action)
|
|
||||||
'''
|
|
||||||
def __init__(self, parser):
|
|
||||||
# Since this is special purposed to just handle help and version, we
|
|
||||||
# take a pre-existing option parser here and set our options from
|
|
||||||
# that. This allows us to give accurate help based on the given
|
|
||||||
# option parser.
|
|
||||||
SortedOptParser.__init__(self, usage=parser.usage,
|
|
||||||
option_list=parser.option_list,
|
|
||||||
option_class=parser.option_class,
|
|
||||||
conflict_handler=parser.conflict_handler,
|
|
||||||
description=parser.description,
|
|
||||||
formatter=parser.formatter,
|
|
||||||
add_help_option=False,
|
|
||||||
prog=parser.prog,
|
|
||||||
epilog=parser.epilog)
|
|
||||||
self.version = parser.version
|
|
||||||
|
|
||||||
def _process_long_opt(self, rargs, values):
|
|
||||||
try:
|
|
||||||
optparse.OptionParser._process_long_opt(self, rargs, values)
|
|
||||||
except optparse.BadOptionError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _process_short_opts(self, rargs, values):
|
|
||||||
try:
|
|
||||||
optparse.OptionParser._process_short_opts(self, rargs, values)
|
|
||||||
except optparse.BadOptionError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
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, runas_prompt_opts=False, desc=None, basedir_opts=False,
|
|
||||||
vault_rekey_opts=False):
|
|
||||||
"""
|
|
||||||
Create an options parser for most ansible scripts
|
|
||||||
"""
|
|
||||||
# base opts
|
|
||||||
parser = SortedOptParser(usage, version=CLI.version("%prog"), description=desc, epilog=epilog)
|
|
||||||
parser.remove_option('--version')
|
|
||||||
version_help = "show program's version number, config file location, configured module search path," \
|
|
||||||
" module location, executable location and exit"
|
|
||||||
parser.add_option('--version', action="version", help=version_help)
|
|
||||||
parser.add_option('-v', '--verbose', dest='verbosity', default=C.DEFAULT_VERBOSITY, action="count",
|
|
||||||
help="verbose mode (-vvv for more, -vvvv to enable connection debugging)")
|
|
||||||
|
|
||||||
if inventory_opts:
|
|
||||||
parser.add_option('-i', '--inventory', '--inventory-file', dest='inventory', action="append",
|
|
||||||
help="specify inventory host path or comma separated host list. --inventory-file is deprecated")
|
|
||||||
parser.add_option('--list-hosts', dest='listhosts', action='store_true',
|
|
||||||
help='outputs a list of matching hosts; does not execute anything else')
|
|
||||||
parser.add_option('-l', '--limit', default=C.DEFAULT_SUBSET, dest='subset',
|
|
||||||
help='further limit selected hosts to an additional pattern')
|
|
||||||
|
|
||||||
if module_opts:
|
|
||||||
parser.add_option('-M', '--module-path', dest='module_path', default=None,
|
|
||||||
help="prepend colon-separated path(s) to module library (default=%s)" % C.DEFAULT_MODULE_PATH,
|
|
||||||
action="callback", callback=CLI.unfrack_paths, type='str')
|
|
||||||
if runtask_opts:
|
|
||||||
parser.add_option('-e', '--extra-vars', dest="extra_vars", action="append",
|
|
||||||
help="set additional variables as key=value or YAML/JSON, if filename prepend with @", default=[])
|
|
||||||
|
|
||||||
if fork_opts:
|
|
||||||
parser.add_option('-f', '--forks', dest='forks', default=C.DEFAULT_FORKS, type='int',
|
|
||||||
help="specify number of parallel processes to use (default=%s)" % C.DEFAULT_FORKS)
|
|
||||||
|
|
||||||
if vault_opts:
|
|
||||||
parser.add_option('--ask-vault-pass', default=C.DEFAULT_ASK_VAULT_PASS, dest='ask_vault_pass', action='store_true',
|
|
||||||
help='ask for vault password')
|
|
||||||
parser.add_option('--vault-password-file', default=[], dest='vault_password_files',
|
|
||||||
help="vault password file", action="callback", callback=CLI.unfrack_paths, type='string')
|
|
||||||
parser.add_option('--vault-id', default=[], dest='vault_ids', action='append', type='string',
|
|
||||||
help='the vault identity to use')
|
|
||||||
|
|
||||||
if vault_rekey_opts:
|
|
||||||
parser.add_option('--new-vault-password-file', default=None, dest='new_vault_password_file',
|
|
||||||
help="new vault password file for rekey", action="callback", callback=CLI.unfrack_path, type='string')
|
|
||||||
parser.add_option('--new-vault-id', default=None, dest='new_vault_id', type='string',
|
|
||||||
help='the new vault identity to use for rekey')
|
|
||||||
|
|
||||||
if subset_opts:
|
|
||||||
parser.add_option('-t', '--tags', dest='tags', default=C.TAGS_RUN, action='append',
|
|
||||||
help="only run plays and tasks tagged with these values")
|
|
||||||
parser.add_option('--skip-tags', dest='skip_tags', default=C.TAGS_SKIP, action='append',
|
|
||||||
help="only run plays and tasks whose tags do not match these values")
|
|
||||||
|
|
||||||
if output_opts:
|
|
||||||
parser.add_option('-o', '--one-line', dest='one_line', action='store_true',
|
|
||||||
help='condense output')
|
|
||||||
parser.add_option('-t', '--tree', dest='tree', default=None,
|
|
||||||
help='log output to this directory')
|
|
||||||
|
|
||||||
if connect_opts:
|
|
||||||
connect_group = optparse.OptionGroup(parser, "Connection Options", "control as whom and how to connect to hosts")
|
|
||||||
connect_group.add_option('-k', '--ask-pass', default=C.DEFAULT_ASK_PASS, dest='ask_pass', action='store_true',
|
|
||||||
help='ask for connection password')
|
|
||||||
connect_group.add_option('--private-key', '--key-file', default=C.DEFAULT_PRIVATE_KEY_FILE, dest='private_key_file',
|
|
||||||
help='use this file to authenticate the connection', action="callback", callback=CLI.unfrack_path, type='string')
|
|
||||||
connect_group.add_option('-u', '--user', default=C.DEFAULT_REMOTE_USER, dest='remote_user',
|
|
||||||
help='connect as this user (default=%s)' % C.DEFAULT_REMOTE_USER)
|
|
||||||
connect_group.add_option('-c', '--connection', dest='connection', default=C.DEFAULT_TRANSPORT,
|
|
||||||
help="connection type to use (default=%s)" % C.DEFAULT_TRANSPORT)
|
|
||||||
connect_group.add_option('-T', '--timeout', default=C.DEFAULT_TIMEOUT, type='int', dest='timeout',
|
|
||||||
help="override the connection timeout in seconds (default=%s)" % C.DEFAULT_TIMEOUT)
|
|
||||||
connect_group.add_option('--ssh-common-args', default='', dest='ssh_common_args',
|
|
||||||
help="specify common arguments to pass to sftp/scp/ssh (e.g. ProxyCommand)")
|
|
||||||
connect_group.add_option('--sftp-extra-args', default='', dest='sftp_extra_args',
|
|
||||||
help="specify extra arguments to pass to sftp only (e.g. -f, -l)")
|
|
||||||
connect_group.add_option('--scp-extra-args', default='', dest='scp_extra_args',
|
|
||||||
help="specify extra arguments to pass to scp only (e.g. -l)")
|
|
||||||
connect_group.add_option('--ssh-extra-args', default='', dest='ssh_extra_args',
|
|
||||||
help="specify extra arguments to pass to ssh only (e.g. -R)")
|
|
||||||
|
|
||||||
parser.add_option_group(connect_group)
|
|
||||||
|
|
||||||
runas_group = None
|
|
||||||
rg = optparse.OptionGroup(parser, "Privilege Escalation Options", "control how and which user you become as on target hosts")
|
|
||||||
if runas_opts:
|
|
||||||
runas_group = rg
|
|
||||||
# priv user defaults to root later on to enable detecting when this option was given here
|
|
||||||
runas_group.add_option("-s", "--sudo", default=C.DEFAULT_SUDO, action="store_true", dest='sudo',
|
|
||||||
help="run operations with sudo (nopasswd) (deprecated, use become)")
|
|
||||||
runas_group.add_option('-U', '--sudo-user', dest='sudo_user', default=None,
|
|
||||||
help='desired sudo user (default=root) (deprecated, use become)')
|
|
||||||
runas_group.add_option('-S', '--su', default=C.DEFAULT_SU, action='store_true',
|
|
||||||
help='run operations with su (deprecated, use become)')
|
|
||||||
runas_group.add_option('-R', '--su-user', default=None,
|
|
||||||
help='run operations with su as this user (default=%s) (deprecated, use become)' % C.DEFAULT_SU_USER)
|
|
||||||
|
|
||||||
# consolidated privilege escalation (become)
|
|
||||||
runas_group.add_option("-b", "--become", default=C.DEFAULT_BECOME, action="store_true", dest='become',
|
|
||||||
help="run operations with become (does not imply password prompting)")
|
|
||||||
runas_group.add_option('--become-method', dest='become_method', default=C.DEFAULT_BECOME_METHOD, type='choice', choices=C.BECOME_METHODS,
|
|
||||||
help="privilege escalation method to use (default=%s), valid choices: [ %s ]" %
|
|
||||||
(C.DEFAULT_BECOME_METHOD, ' | '.join(C.BECOME_METHODS)))
|
|
||||||
runas_group.add_option('--become-user', default=None, dest='become_user', type='string',
|
|
||||||
help='run operations as this user (default=%s)' % C.DEFAULT_BECOME_USER)
|
|
||||||
|
|
||||||
if runas_opts or runas_prompt_opts:
|
|
||||||
if not runas_group:
|
|
||||||
runas_group = rg
|
|
||||||
runas_group.add_option('--ask-sudo-pass', default=C.DEFAULT_ASK_SUDO_PASS, dest='ask_sudo_pass', action='store_true',
|
|
||||||
help='ask for sudo password (deprecated, use become)')
|
|
||||||
runas_group.add_option('--ask-su-pass', default=C.DEFAULT_ASK_SU_PASS, dest='ask_su_pass', action='store_true',
|
|
||||||
help='ask for su password (deprecated, use become)')
|
|
||||||
runas_group.add_option('-K', '--ask-become-pass', default=False, dest='become_ask_pass', action='store_true',
|
|
||||||
help='ask for privilege escalation password')
|
|
||||||
|
|
||||||
if runas_group:
|
|
||||||
parser.add_option_group(runas_group)
|
|
||||||
|
|
||||||
if async_opts:
|
|
||||||
parser.add_option('-P', '--poll', default=C.DEFAULT_POLL_INTERVAL, type='int', dest='poll_interval',
|
|
||||||
help="set the poll interval if using -B (default=%s)" % C.DEFAULT_POLL_INTERVAL)
|
|
||||||
parser.add_option('-B', '--background', dest='seconds', type='int', default=0,
|
|
||||||
help='run asynchronously, failing after X seconds (default=N/A)')
|
|
||||||
|
|
||||||
if check_opts:
|
|
||||||
parser.add_option("-C", "--check", default=False, dest='check', action='store_true',
|
|
||||||
help="don't make any changes; instead, try to predict some of the changes that may occur")
|
|
||||||
parser.add_option('--syntax-check', dest='syntax', action='store_true',
|
|
||||||
help="perform a syntax check on the playbook, but do not execute it")
|
|
||||||
parser.add_option("-D", "--diff", default=C.DIFF_ALWAYS, dest='diff', action='store_true',
|
|
||||||
help="when changing (small) files and templates, show the differences in those files; works great with --check")
|
|
||||||
|
|
||||||
if meta_opts:
|
|
||||||
parser.add_option('--force-handlers', default=C.DEFAULT_FORCE_HANDLERS, dest='force_handlers', action='store_true',
|
|
||||||
help="run handlers even if a task fails")
|
|
||||||
parser.add_option('--flush-cache', dest='flush_cache', action='store_true',
|
|
||||||
help="clear the fact cache for every host in inventory")
|
|
||||||
|
|
||||||
if basedir_opts:
|
|
||||||
parser.add_option('--playbook-dir', default=None, dest='basedir', action='store',
|
|
||||||
help="Since this tool does not use playbooks, use this as a subsitute playbook directory."
|
|
||||||
"This sets the relative path for many features including roles/ group_vars/ etc.")
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
|
|
||||||
class CLI(with_metaclass(ABCMeta, object)):
|
class CLI(with_metaclass(ABCMeta, object)):
|
||||||
''' code behind bin/ansible* programs '''
|
''' code behind bin/ansible* programs '''
|
||||||
|
|
||||||
|
@ -277,7 +80,7 @@ class CLI(with_metaclass(ABCMeta, object)):
|
||||||
# the standard OptionParser throws an error for unknown options and
|
# the standard OptionParser throws an error for unknown options and
|
||||||
# without knowing action, we only know of a subset of the options
|
# without knowing action, we only know of a subset of the options
|
||||||
# that could be legal for this command
|
# that could be legal for this command
|
||||||
tmp_parser = InvalidOptsParser(self.parser)
|
tmp_parser = opt_help.InvalidOptsParser(self.parser)
|
||||||
tmp_options, tmp_args = tmp_parser.parse_args(self.args)
|
tmp_options, tmp_args = tmp_parser.parse_args(self.args)
|
||||||
if not(hasattr(tmp_options, 'help') and tmp_options.help) or (hasattr(tmp_options, 'version') and tmp_options.version):
|
if not(hasattr(tmp_options, 'help') and tmp_options.help) or (hasattr(tmp_options, 'version') and tmp_options.version):
|
||||||
raise AnsibleOptionsError("Missing required action")
|
raise AnsibleOptionsError("Missing required action")
|
||||||
|
@ -533,34 +336,8 @@ class CLI(with_metaclass(ABCMeta, object)):
|
||||||
|
|
||||||
return op
|
return op
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def unfrack_paths(option, opt, value, parser):
|
|
||||||
paths = getattr(parser.values, option.dest)
|
|
||||||
if paths is None:
|
|
||||||
paths = []
|
|
||||||
|
|
||||||
if isinstance(value, string_types):
|
|
||||||
paths[:0] = [unfrackpath(x) for x in value.split(os.pathsep) if x]
|
|
||||||
elif isinstance(value, list):
|
|
||||||
paths[:0] = [unfrackpath(x) for x in value if x]
|
|
||||||
else:
|
|
||||||
pass # FIXME: should we raise options error?
|
|
||||||
|
|
||||||
setattr(parser.values, option.dest, paths)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def unfrack_path(option, opt, value, parser):
|
|
||||||
if value != '-':
|
|
||||||
setattr(parser.values, option.dest, unfrackpath(value))
|
|
||||||
else:
|
|
||||||
setattr(parser.values, option.dest, value)
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def init_parser(self, usage="", output_opts=False, runas_opts=False, meta_opts=False,
|
def init_parser(self, usage="", desc=None, epilog=None):
|
||||||
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, runas_prompt_opts=False, desc=None,
|
|
||||||
basedir_opts=False, vault_rekey_opts=False):
|
|
||||||
"""
|
"""
|
||||||
Create an options parser for most ansible scripts
|
Create an options parser for most ansible scripts
|
||||||
|
|
||||||
|
@ -570,19 +347,11 @@ class CLI(with_metaclass(ABCMeta, object)):
|
||||||
An implementation will look something like this::
|
An implementation will look something like this::
|
||||||
|
|
||||||
def init_parser(self):
|
def init_parser(self):
|
||||||
self.parser = super(MyCLI, self).init__parser(usage="My Ansible CLI", inventory_opts=True)
|
super(MyCLI, self).init__parser(usage="My Ansible CLI", inventory_opts=True)
|
||||||
|
ansible.arguments.optparse_helpers.add_runas_options(self.parser)
|
||||||
self.parser.add_option('--my-option', dest='my_option', action='store')
|
self.parser.add_option('--my-option', dest='my_option', action='store')
|
||||||
return self.parser
|
|
||||||
"""
|
"""
|
||||||
self.parser = base_parser(usage=usage, output_opts=output_opts, runas_opts=runas_opts,
|
self.parser = opt_help.create_base_parser(usage=usage, desc=desc, epilog=epilog)
|
||||||
meta_opts=meta_opts, runtask_opts=runtask_opts,
|
|
||||||
vault_opts=vault_opts, module_opts=module_opts,
|
|
||||||
async_opts=async_opts, connect_opts=connect_opts,
|
|
||||||
subset_opts=subset_opts, check_opts=check_opts,
|
|
||||||
inventory_opts=inventory_opts, epilog=epilog, fork_opts=fork_opts,
|
|
||||||
runas_prompt_opts=runas_prompt_opts, desc=desc,
|
|
||||||
basedir_opts=basedir_opts, vault_rekey_opts=vault_rekey_opts)
|
|
||||||
return self.parser
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def post_process_args(self, options, args):
|
def post_process_args(self, options, args):
|
||||||
|
@ -654,30 +423,12 @@ class CLI(with_metaclass(ABCMeta, object)):
|
||||||
options.args = args
|
options.args = args
|
||||||
context._init_global_context(options)
|
context._init_global_context(options)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def version(prog):
|
|
||||||
''' return ansible version '''
|
|
||||||
result = "{0} {1}".format(prog, __version__)
|
|
||||||
gitinfo = CLI._gitinfo()
|
|
||||||
if gitinfo:
|
|
||||||
result = result + " {0}".format(gitinfo)
|
|
||||||
result += "\n config file = %s" % C.CONFIG_FILE
|
|
||||||
if C.DEFAULT_MODULE_PATH is None:
|
|
||||||
cpath = "Default w/o overrides"
|
|
||||||
else:
|
|
||||||
cpath = C.DEFAULT_MODULE_PATH
|
|
||||||
result = result + "\n configured module search path = %s" % cpath
|
|
||||||
result = result + "\n ansible python module location = %s" % ':'.join(ansible.__path__)
|
|
||||||
result = result + "\n executable location = %s" % sys.argv[0]
|
|
||||||
result = result + "\n python version = %s" % ''.join(sys.version.splitlines())
|
|
||||||
return result
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def version_info(gitinfo=False):
|
def version_info(gitinfo=False):
|
||||||
''' return full ansible version info '''
|
''' return full ansible version info '''
|
||||||
if gitinfo:
|
if gitinfo:
|
||||||
# expensive call, user with care
|
# expensive call, user with care
|
||||||
ansible_version_string = CLI.version('')
|
ansible_version_string = opt_help.version()
|
||||||
else:
|
else:
|
||||||
ansible_version_string = __version__
|
ansible_version_string = __version__
|
||||||
ansible_version = ansible_version_string.split()[0]
|
ansible_version = ansible_version_string.split()[0]
|
||||||
|
@ -698,70 +449,6 @@ class CLI(with_metaclass(ABCMeta, object)):
|
||||||
'minor': ansible_versions[1],
|
'minor': ansible_versions[1],
|
||||||
'revision': ansible_versions[2]}
|
'revision': ansible_versions[2]}
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _git_repo_info(repo_path):
|
|
||||||
''' returns a string containing git branch, commit id and commit date '''
|
|
||||||
result = None
|
|
||||||
if os.path.exists(repo_path):
|
|
||||||
# Check if the .git is a file. If it is a file, it means that we are in a submodule structure.
|
|
||||||
if os.path.isfile(repo_path):
|
|
||||||
try:
|
|
||||||
gitdir = yaml.safe_load(open(repo_path)).get('gitdir')
|
|
||||||
# There is a possibility the .git file to have an absolute path.
|
|
||||||
if os.path.isabs(gitdir):
|
|
||||||
repo_path = gitdir
|
|
||||||
else:
|
|
||||||
repo_path = os.path.join(repo_path[:-4], gitdir)
|
|
||||||
except (IOError, AttributeError):
|
|
||||||
return ''
|
|
||||||
f = open(os.path.join(repo_path, "HEAD"))
|
|
||||||
line = f.readline().rstrip("\n")
|
|
||||||
if line.startswith("ref:"):
|
|
||||||
branch_path = os.path.join(repo_path, line[5:])
|
|
||||||
else:
|
|
||||||
branch_path = None
|
|
||||||
f.close()
|
|
||||||
if branch_path and os.path.exists(branch_path):
|
|
||||||
branch = '/'.join(line.split('/')[2:])
|
|
||||||
f = open(branch_path)
|
|
||||||
commit = f.readline()[:10]
|
|
||||||
f.close()
|
|
||||||
else:
|
|
||||||
# detached HEAD
|
|
||||||
commit = line[:10]
|
|
||||||
branch = 'detached HEAD'
|
|
||||||
branch_path = os.path.join(repo_path, "HEAD")
|
|
||||||
|
|
||||||
date = time.localtime(os.stat(branch_path).st_mtime)
|
|
||||||
if time.daylight == 0:
|
|
||||||
offset = time.timezone
|
|
||||||
else:
|
|
||||||
offset = time.altzone
|
|
||||||
result = "({0} {1}) last updated {2} (GMT {3:+04d})".format(branch, commit, time.strftime("%Y/%m/%d %H:%M:%S", date), int(offset / -36))
|
|
||||||
else:
|
|
||||||
result = ''
|
|
||||||
return result
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _gitinfo():
|
|
||||||
basedir = os.path.join(os.path.dirname(__file__), '..', '..', '..')
|
|
||||||
repo_path = os.path.join(basedir, '.git')
|
|
||||||
result = CLI._git_repo_info(repo_path)
|
|
||||||
submodules = os.path.join(basedir, '.gitmodules')
|
|
||||||
if not os.path.exists(submodules):
|
|
||||||
return result
|
|
||||||
f = open(submodules)
|
|
||||||
for line in f:
|
|
||||||
tokens = line.strip().split(' ')
|
|
||||||
if tokens[0] == 'path':
|
|
||||||
submodule_path = tokens[2]
|
|
||||||
submodule_info = CLI._git_repo_info(os.path.join(basedir, submodule_path, '.git'))
|
|
||||||
if not submodule_info:
|
|
||||||
submodule_info = ' not found - use git submodule update --init ' + submodule_path
|
|
||||||
result += "\n {0}: {1}".format(submodule_path, submodule_info)
|
|
||||||
f.close()
|
|
||||||
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
|
||||||
|
|
|
@ -7,6 +7,7 @@ __metaclass__ = type
|
||||||
|
|
||||||
from ansible import constants as C
|
from ansible import constants as C
|
||||||
from ansible import context
|
from ansible import context
|
||||||
|
from ansible.arguments import optparse_helpers as opt_help
|
||||||
from ansible.cli import CLI
|
from ansible.cli import CLI
|
||||||
from ansible.errors import AnsibleError, AnsibleOptionsError
|
from ansible.errors import AnsibleError, AnsibleOptionsError
|
||||||
from ansible.executor.task_queue_manager import TaskQueueManager
|
from ansible.executor.task_queue_manager import TaskQueueManager
|
||||||
|
@ -27,22 +28,23 @@ class AdHocCLI(CLI):
|
||||||
|
|
||||||
def init_parser(self):
|
def init_parser(self):
|
||||||
''' create an options parser for bin/ansible '''
|
''' create an options parser for bin/ansible '''
|
||||||
self.parser = super(AdHocCLI, self).init_parser(
|
super(AdHocCLI, self).init_parser(usage='%prog <host-pattern> [options]',
|
||||||
usage='%prog <host-pattern> [options]',
|
desc="Define and run a single task 'playbook' against"
|
||||||
runas_opts=True,
|
" a set of hosts",
|
||||||
inventory_opts=True,
|
epilog="Some modules do not make sense in Ad-Hoc (include,"
|
||||||
async_opts=True,
|
" meta, etc)")
|
||||||
output_opts=True,
|
|
||||||
connect_opts=True,
|
opt_help.add_runas_options(self.parser)
|
||||||
check_opts=True,
|
opt_help.add_inventory_options(self.parser)
|
||||||
runtask_opts=True,
|
opt_help.add_async_options(self.parser)
|
||||||
vault_opts=True,
|
opt_help.add_output_options(self.parser)
|
||||||
fork_opts=True,
|
opt_help.add_connect_options(self.parser)
|
||||||
module_opts=True,
|
opt_help.add_check_options(self.parser)
|
||||||
basedir_opts=True,
|
opt_help.add_runtask_options(self.parser)
|
||||||
desc="Define and run a single task 'playbook' against a set of hosts",
|
opt_help.add_vault_options(self.parser)
|
||||||
epilog="Some modules do not make sense in Ad-Hoc (include, meta, etc)",
|
opt_help.add_fork_options(self.parser)
|
||||||
)
|
opt_help.add_module_options(self.parser)
|
||||||
|
opt_help.add_basedir_options(self.parser)
|
||||||
|
|
||||||
# options unique to ansible ad-hoc
|
# options unique to ansible ad-hoc
|
||||||
self.parser.add_option('-a', '--args', dest='module_args',
|
self.parser.add_option('-a', '--args', dest='module_args',
|
||||||
|
@ -50,7 +52,6 @@ class AdHocCLI(CLI):
|
||||||
self.parser.add_option('-m', '--module-name', dest='module_name',
|
self.parser.add_option('-m', '--module-name', dest='module_name',
|
||||||
help="module name to execute (default=%s)" % C.DEFAULT_MODULE_NAME,
|
help="module name to execute (default=%s)" % C.DEFAULT_MODULE_NAME,
|
||||||
default=C.DEFAULT_MODULE_NAME)
|
default=C.DEFAULT_MODULE_NAME)
|
||||||
return self.parser
|
|
||||||
|
|
||||||
def post_process_args(self, options, args):
|
def post_process_args(self, options, args):
|
||||||
'''Post process and validate options for bin/ansible '''
|
'''Post process and validate options for bin/ansible '''
|
||||||
|
|
|
@ -36,29 +36,31 @@ class ConfigCLI(CLI):
|
||||||
|
|
||||||
def init_parser(self):
|
def init_parser(self):
|
||||||
|
|
||||||
self.parser = super(ConfigCLI, self).init_parser(
|
super(ConfigCLI, self).init_parser(
|
||||||
usage="usage: %%prog [%s] [--help] [options] [ansible.cfg]" % "|".join(sorted(self.VALID_ACTIONS)),
|
usage="usage: %%prog [%s] [--help] [options] [ansible.cfg]" % "|".join(sorted(self.VALID_ACTIONS)),
|
||||||
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]),
|
||||||
desc="View, edit, and manage ansible configuration.",
|
desc="View, edit, and manage ansible configuration.",
|
||||||
)
|
)
|
||||||
self.parser.add_option('-c', '--config', dest='config_file', help="path to configuration file, defaults to first file found in precedence.")
|
self.parser.add_option('-c', '--config', dest='config_file',
|
||||||
|
help="path to configuration file, defaults to first file found in precedence.")
|
||||||
|
|
||||||
self.set_action()
|
self.set_action()
|
||||||
|
|
||||||
# options specific to self.actions
|
# options specific to self.actions
|
||||||
if self.action == "list":
|
if self.action == "list":
|
||||||
self.parser.set_usage("usage: %prog list [options] ")
|
self.parser.set_usage("usage: %prog list [options] ")
|
||||||
if self.action == "dump":
|
|
||||||
|
elif self.action == "dump":
|
||||||
self.parser.add_option('--only-changed', dest='only_changed', action='store_true',
|
self.parser.add_option('--only-changed', dest='only_changed', action='store_true',
|
||||||
help="Only show configurations that have changed from the default")
|
help="Only show configurations that have changed from the default")
|
||||||
|
|
||||||
elif self.action == "update":
|
elif self.action == "update":
|
||||||
self.parser.add_option('-s', '--setting', dest='setting', help="config setting, the section defaults to 'defaults'")
|
self.parser.add_option('-s', '--setting', dest='setting', help="config setting, the section defaults to 'defaults'")
|
||||||
self.parser.set_usage("usage: %prog update [options] [-c ansible.cfg] -s '[section.]setting=value'")
|
self.parser.set_usage("usage: %prog update [options] [-c ansible.cfg] -s '[section.]setting=value'")
|
||||||
|
|
||||||
elif self.action == "search":
|
elif self.action == "search":
|
||||||
self.parser.set_usage("usage: %prog update [options] [-c ansible.cfg] <search term>")
|
self.parser.set_usage("usage: %prog update [options] [-c ansible.cfg] <search term>")
|
||||||
|
|
||||||
return self.parser
|
|
||||||
|
|
||||||
def post_process_args(self, options, args):
|
def post_process_args(self, options, args):
|
||||||
super(ConfigCLI, self).post_process_args(options, args)
|
super(ConfigCLI, self).post_process_args(options, args)
|
||||||
display.verbosity = options.verbosity
|
display.verbosity = options.verbosity
|
||||||
|
|
|
@ -26,6 +26,7 @@ import sys
|
||||||
|
|
||||||
from ansible import constants as C
|
from ansible import constants as C
|
||||||
from ansible import context
|
from ansible import context
|
||||||
|
from ansible.arguments import optparse_helpers as opt_help
|
||||||
from ansible.cli import CLI
|
from ansible.cli import CLI
|
||||||
from ansible.executor.task_queue_manager import TaskQueueManager
|
from ansible.executor.task_queue_manager import TaskQueueManager
|
||||||
from ansible.module_utils._text import to_native, to_text
|
from ansible.module_utils._text import to_native, to_text
|
||||||
|
@ -80,24 +81,22 @@ class ConsoleCLI(CLI, cmd.Cmd):
|
||||||
def init_parser(self):
|
def init_parser(self):
|
||||||
super(ConsoleCLI, self).init_parser(
|
super(ConsoleCLI, self).init_parser(
|
||||||
usage='%prog [<host-pattern>] [options]',
|
usage='%prog [<host-pattern>] [options]',
|
||||||
runas_opts=True,
|
|
||||||
inventory_opts=True,
|
|
||||||
connect_opts=True,
|
|
||||||
check_opts=True,
|
|
||||||
vault_opts=True,
|
|
||||||
fork_opts=True,
|
|
||||||
module_opts=True,
|
|
||||||
basedir_opts=True,
|
|
||||||
desc="REPL console for executing Ansible tasks.",
|
desc="REPL console for executing Ansible tasks.",
|
||||||
epilog="This is not a live session/connection, each task executes in the background and returns it's results."
|
epilog="This is not a live session/connection, each task executes in the background and returns it's results."
|
||||||
)
|
)
|
||||||
|
opt_help.add_runas_options(self.parser)
|
||||||
|
opt_help.add_inventory_options(self.parser)
|
||||||
|
opt_help.add_connect_options(self.parser)
|
||||||
|
opt_help.add_check_options(self.parser)
|
||||||
|
opt_help.add_vault_options(self.parser)
|
||||||
|
opt_help.add_fork_options(self.parser)
|
||||||
|
opt_help.add_module_options(self.parser)
|
||||||
|
opt_help.add_basedir_options(self.parser)
|
||||||
|
|
||||||
# options unique to shell
|
# options unique to shell
|
||||||
self.parser.add_option('--step', dest='step', action='store_true',
|
self.parser.add_option('--step', dest='step', action='store_true',
|
||||||
help="one-step-at-a-time: confirm each task before running")
|
help="one-step-at-a-time: confirm each task before running")
|
||||||
|
|
||||||
return self.parser
|
|
||||||
|
|
||||||
def post_process_args(self, options, args):
|
def post_process_args(self, options, args):
|
||||||
options, args = super(ConsoleCLI, self).post_process_args(options, args)
|
options, args = super(ConsoleCLI, self).post_process_args(options, args)
|
||||||
display.verbosity = options.verbosity
|
display.verbosity = options.verbosity
|
||||||
|
|
|
@ -16,6 +16,7 @@ import ansible.plugins.loader as plugin_loader
|
||||||
|
|
||||||
from ansible import constants as C
|
from ansible import constants as C
|
||||||
from ansible import context
|
from ansible import context
|
||||||
|
from ansible.arguments import optparse_helpers as opt_help
|
||||||
from ansible.cli import CLI
|
from ansible.cli import CLI
|
||||||
from ansible.errors import AnsibleError, AnsibleOptionsError
|
from ansible.errors import AnsibleError, AnsibleOptionsError
|
||||||
from ansible.module_utils._text import to_native
|
from ansible.module_utils._text import to_native
|
||||||
|
@ -47,12 +48,12 @@ class DocCLI(CLI):
|
||||||
|
|
||||||
def init_parser(self):
|
def init_parser(self):
|
||||||
|
|
||||||
self.parser = super(DocCLI, self).init_parser(
|
super(DocCLI, self).init_parser(
|
||||||
usage='usage: %prog [-l|-F|-s] [options] [-t <plugin type> ] [plugin]',
|
usage='usage: %prog [-l|-F|-s] [options] [-t <plugin type> ] [plugin]',
|
||||||
module_opts=True,
|
|
||||||
desc="plugin documentation tool",
|
desc="plugin documentation tool",
|
||||||
epilog="See man pages for Ansible CLI options or website for tutorials https://docs.ansible.com"
|
epilog="See man pages for Ansible CLI options or website for tutorials https://docs.ansible.com"
|
||||||
)
|
)
|
||||||
|
opt_help.add_module_options(self.parser)
|
||||||
|
|
||||||
self.parser.add_option("-F", "--list_files", action="store_true", default=False, dest="list_files",
|
self.parser.add_option("-F", "--list_files", action="store_true", default=False, dest="list_files",
|
||||||
help='Show plugin names and their source files without summaries (implies --list)')
|
help='Show plugin names and their source files without summaries (implies --list)')
|
||||||
|
@ -68,7 +69,6 @@ class DocCLI(CLI):
|
||||||
help='Choose which plugin type (defaults to "module"). '
|
help='Choose which plugin type (defaults to "module"). '
|
||||||
'Available plugin types are : {0}'.format(C.DOCUMENTABLE_PLUGINS),
|
'Available plugin types are : {0}'.format(C.DOCUMENTABLE_PLUGINS),
|
||||||
choices=C.DOCUMENTABLE_PLUGINS)
|
choices=C.DOCUMENTABLE_PLUGINS)
|
||||||
return self.parser
|
|
||||||
|
|
||||||
def post_process_args(self, options, args):
|
def post_process_args(self, options, args):
|
||||||
if [options.all_plugins, options.json_dump, options.list_dir, options.list_files, options.show_snippet].count(True) > 1:
|
if [options.all_plugins, options.json_dump, options.list_dir, options.list_files, options.show_snippet].count(True) > 1:
|
||||||
|
|
|
@ -14,8 +14,9 @@ import yaml
|
||||||
|
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
|
||||||
from ansible import context
|
|
||||||
import ansible.constants as C
|
import ansible.constants as C
|
||||||
|
from ansible import context
|
||||||
|
from ansible.arguments import optparse_helpers as opt_help
|
||||||
from ansible.cli import CLI
|
from ansible.cli import CLI
|
||||||
from ansible.errors import AnsibleError, AnsibleOptionsError
|
from ansible.errors import AnsibleError, AnsibleOptionsError
|
||||||
from ansible.galaxy import Galaxy
|
from ansible.galaxy import Galaxy
|
||||||
|
@ -109,7 +110,7 @@ class GalaxyCLI(CLI):
|
||||||
if self.action not in ("delete", "import", "init", "login", "setup"):
|
if self.action not in ("delete", "import", "init", "login", "setup"):
|
||||||
# NOTE: while the option type=str, the default is a list, and the
|
# NOTE: while the option type=str, the default is a list, and the
|
||||||
# callback will set the value to a list.
|
# callback will set the value to a list.
|
||||||
self.parser.add_option('-p', '--roles-path', dest='roles_path', action="callback", callback=CLI.unfrack_paths, default=C.DEFAULT_ROLES_PATH,
|
self.parser.add_option('-p', '--roles-path', dest='roles_path', action="callback", callback=opt_help.unfrack_paths, default=C.DEFAULT_ROLES_PATH,
|
||||||
help='The path to the directory containing your roles. The default is the roles_path configured in your ansible.cfg'
|
help='The path to the directory containing your roles. The default is the roles_path configured in your ansible.cfg'
|
||||||
' file (/etc/ansible/roles if not configured)', type='str')
|
' file (/etc/ansible/roles if not configured)', type='str')
|
||||||
if self.action in ("init", "install"):
|
if self.action in ("init", "install"):
|
||||||
|
@ -118,7 +119,7 @@ class GalaxyCLI(CLI):
|
||||||
def init_parser(self):
|
def init_parser(self):
|
||||||
''' create an options parser for bin/ansible '''
|
''' create an options parser for bin/ansible '''
|
||||||
|
|
||||||
self.parser = super(GalaxyCLI, self).init_parser(
|
super(GalaxyCLI, self).init_parser(
|
||||||
usage="usage: %%prog [%s] [--help] [options] ..." % "|".join(sorted(self.VALID_ACTIONS)),
|
usage="usage: %%prog [%s] [--help] [options] ..." % "|".join(sorted(self.VALID_ACTIONS)),
|
||||||
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]),
|
||||||
desc="Perform various Role related operations.",
|
desc="Perform various Role related operations.",
|
||||||
|
@ -130,8 +131,6 @@ class GalaxyCLI(CLI):
|
||||||
help='Ignore SSL certificate validation errors.')
|
help='Ignore SSL certificate validation errors.')
|
||||||
self.set_action()
|
self.set_action()
|
||||||
|
|
||||||
return self.parser
|
|
||||||
|
|
||||||
def post_process_args(self, options, args):
|
def post_process_args(self, options, args):
|
||||||
options, args = super(GalaxyCLI, self).post_process_args(options, args)
|
options, args = super(GalaxyCLI, self).post_process_args(options, args)
|
||||||
display.verbosity = options.verbosity
|
display.verbosity = options.verbosity
|
||||||
|
|
|
@ -10,11 +10,11 @@ from operator import attrgetter
|
||||||
|
|
||||||
from ansible import constants as C
|
from ansible import constants as C
|
||||||
from ansible import context
|
from ansible import context
|
||||||
|
from ansible.arguments import optparse_helpers as opt_help
|
||||||
from ansible.cli import CLI
|
from ansible.cli import CLI
|
||||||
from ansible.errors import AnsibleError, AnsibleOptionsError
|
from ansible.errors import AnsibleError, AnsibleOptionsError
|
||||||
from ansible.inventory.host import Host
|
from ansible.inventory.host import Host
|
||||||
from ansible.plugins.loader import vars_loader
|
from ansible.plugins.loader import vars_loader
|
||||||
from ansible.parsing.dataloader import DataLoader
|
|
||||||
from ansible.utils.vars import combine_vars
|
from ansible.utils.vars import combine_vars
|
||||||
from ansible.utils.display import Display
|
from ansible.utils.display import Display
|
||||||
|
|
||||||
|
@ -57,13 +57,13 @@ class InventoryCLI(CLI):
|
||||||
|
|
||||||
def init_parser(self):
|
def init_parser(self):
|
||||||
|
|
||||||
self.parser = super(InventoryCLI, self).init_parser(
|
super(InventoryCLI, self).init_parser(
|
||||||
usage='usage: %prog [options] [host|group]',
|
usage='usage: %prog [options] [host|group]',
|
||||||
epilog='Show Ansible inventory information, by default it uses the inventory script JSON format',
|
epilog='Show Ansible inventory information, by default it uses the inventory script JSON format')
|
||||||
inventory_opts=True,
|
|
||||||
vault_opts=True,
|
opt_help.add_inventory_options(self.parser)
|
||||||
basedir_opts=True,
|
opt_help.add_vault_options(self.parser)
|
||||||
)
|
opt_help.add_basedir_options(self.parser)
|
||||||
|
|
||||||
# remove unused default options
|
# remove unused default options
|
||||||
self.parser.remove_option('--limit')
|
self.parser.remove_option('--limit')
|
||||||
|
@ -92,8 +92,6 @@ class InventoryCLI(CLI):
|
||||||
# self.parser.add_option("--ignore-vars-plugins", action="store_true", default=False, dest='ignore_vars_plugins',
|
# self.parser.add_option("--ignore-vars-plugins", action="store_true", default=False, dest='ignore_vars_plugins',
|
||||||
# help="When doing an --list, skip vars data from vars plugins, by default, this would include group_vars/ and host_vars/")
|
# help="When doing an --list, skip vars data from vars plugins, by default, this would include group_vars/ and host_vars/")
|
||||||
|
|
||||||
return self.parser
|
|
||||||
|
|
||||||
def post_process_args(self, options, args):
|
def post_process_args(self, options, args):
|
||||||
display.verbosity = options.verbosity
|
display.verbosity = options.verbosity
|
||||||
self.validate_conflicts(options, vault_opts=True)
|
self.validate_conflicts(options, vault_opts=True)
|
||||||
|
|
|
@ -1,19 +1,6 @@
|
||||||
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
|
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
|
||||||
#
|
# Copyright: (c) 2018, Ansible Project
|
||||||
# This file is part of Ansible
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
#
|
|
||||||
# Ansible is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# Ansible is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
from __future__ import (absolute_import, division, print_function)
|
from __future__ import (absolute_import, division, print_function)
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
@ -22,6 +9,7 @@ import os
|
||||||
import stat
|
import stat
|
||||||
|
|
||||||
from ansible import context
|
from ansible import context
|
||||||
|
from ansible.arguments import optparse_helpers as opt_help
|
||||||
from ansible.cli import CLI
|
from ansible.cli import CLI
|
||||||
from ansible.errors import AnsibleError, AnsibleOptionsError
|
from ansible.errors import AnsibleError, AnsibleOptionsError
|
||||||
from ansible.executor.playbook_executor import PlaybookExecutor
|
from ansible.executor.playbook_executor import PlaybookExecutor
|
||||||
|
@ -41,18 +29,18 @@ class PlaybookCLI(CLI):
|
||||||
# create parser for CLI options
|
# create parser for CLI options
|
||||||
super(PlaybookCLI, self).init_parser(
|
super(PlaybookCLI, self).init_parser(
|
||||||
usage="%prog [options] playbook.yml [playbook2 ...]",
|
usage="%prog [options] playbook.yml [playbook2 ...]",
|
||||||
connect_opts=True,
|
desc="Runs Ansible playbooks, executing the defined tasks on the targeted hosts.")
|
||||||
meta_opts=True,
|
|
||||||
runas_opts=True,
|
opt_help.add_connect_options(self.parser)
|
||||||
subset_opts=True,
|
opt_help.add_meta_options(self.parser)
|
||||||
check_opts=True,
|
opt_help.add_runas_options(self.parser)
|
||||||
inventory_opts=True,
|
opt_help.add_subset_options(self.parser)
|
||||||
runtask_opts=True,
|
opt_help.add_check_options(self.parser)
|
||||||
vault_opts=True,
|
opt_help.add_inventory_options(self.parser)
|
||||||
fork_opts=True,
|
opt_help.add_runtask_options(self.parser)
|
||||||
module_opts=True,
|
opt_help.add_vault_options(self.parser)
|
||||||
desc="Runs Ansible playbooks, executing the defined tasks on the targeted hosts.",
|
opt_help.add_fork_options(self.parser)
|
||||||
)
|
opt_help.add_module_options(self.parser)
|
||||||
|
|
||||||
# ansible playbook specific opts
|
# ansible playbook specific opts
|
||||||
self.parser.add_option('--list-tasks', dest='listtasks', action='store_true',
|
self.parser.add_option('--list-tasks', dest='listtasks', action='store_true',
|
||||||
|
@ -64,8 +52,6 @@ class PlaybookCLI(CLI):
|
||||||
self.parser.add_option('--start-at-task', dest='start_at_task',
|
self.parser.add_option('--start-at-task', dest='start_at_task',
|
||||||
help="start the playbook at the task matching this name")
|
help="start the playbook at the task matching this name")
|
||||||
|
|
||||||
return self.parser
|
|
||||||
|
|
||||||
def post_process_args(self, options, args):
|
def post_process_args(self, options, args):
|
||||||
options, args = super(PlaybookCLI, self).post_process_args(options, args)
|
options, args = super(PlaybookCLI, self).post_process_args(options, args)
|
||||||
|
|
||||||
|
@ -111,7 +97,7 @@ class PlaybookCLI(CLI):
|
||||||
# limit if only implicit localhost was in inventory to start with.
|
# limit if only implicit localhost was in inventory to start with.
|
||||||
#
|
#
|
||||||
# Fix this when we rewrite inventory by making localhost a real host (and thus show up in list_hosts())
|
# Fix this when we rewrite inventory by making localhost a real host (and thus show up in list_hosts())
|
||||||
hosts = super(PlaybookCLI, self).get_host_list(inventory, context.CLIARGS['subset'])
|
hosts = self.get_host_list(inventory, context.CLIARGS['subset'])
|
||||||
|
|
||||||
# flush fact cache if requested
|
# flush fact cache if requested
|
||||||
if context.CLIARGS['flush_cache']:
|
if context.CLIARGS['flush_cache']:
|
||||||
|
|
|
@ -16,6 +16,7 @@ import time
|
||||||
|
|
||||||
from ansible import constants as C
|
from ansible import constants as C
|
||||||
from ansible import context
|
from ansible import context
|
||||||
|
from ansible.arguments import optparse_helpers as opt_help
|
||||||
from ansible.cli import CLI
|
from ansible.cli import CLI
|
||||||
from ansible.errors import AnsibleOptionsError
|
from ansible.errors import AnsibleOptionsError
|
||||||
from ansible.module_utils._text import to_native, to_text
|
from ansible.module_utils._text import to_native, to_text
|
||||||
|
@ -68,18 +69,18 @@ class PullCLI(CLI):
|
||||||
def init_parser(self):
|
def init_parser(self):
|
||||||
''' create an options parser for bin/ansible '''
|
''' create an options parser for bin/ansible '''
|
||||||
|
|
||||||
self.parser = super(PullCLI, self).init_parser(
|
super(PullCLI, self).init_parser(
|
||||||
usage='%prog -U <repository> [options] [<playbook.yml>]',
|
usage='%prog -U <repository> [options] [<playbook.yml>]',
|
||||||
connect_opts=True,
|
desc="pulls playbooks from a VCS repo and executes them for the local host")
|
||||||
vault_opts=True,
|
|
||||||
runtask_opts=True,
|
# Do not add check_options as there's a conflict with --checkout/-C
|
||||||
subset_opts=True,
|
opt_help.add_connect_options(self.parser)
|
||||||
check_opts=False, # prevents conflict of --checkout/-C and --check/-C
|
opt_help.add_vault_options(self.parser)
|
||||||
inventory_opts=True,
|
opt_help.add_runtask_options(self.parser)
|
||||||
module_opts=True,
|
opt_help.add_subset_options(self.parser)
|
||||||
runas_prompt_opts=True,
|
opt_help.add_inventory_options(self.parser)
|
||||||
desc="pulls playbooks from a VCS repo and executes them for the local host",
|
opt_help.add_module_options(self.parser)
|
||||||
)
|
opt_help.add_runas_prompt_options(self.parser)
|
||||||
|
|
||||||
# options unique to pull
|
# options unique to pull
|
||||||
self.parser.add_option('--purge', default=False, action='store_true', help='purge checkout after playbook run')
|
self.parser.add_option('--purge', default=False, action='store_true', help='purge checkout after playbook run')
|
||||||
|
@ -114,8 +115,6 @@ class PullCLI(CLI):
|
||||||
self.parser.add_option("--diff", default=C.DIFF_ALWAYS, dest='diff', action='store_true',
|
self.parser.add_option("--diff", default=C.DIFF_ALWAYS, dest='diff', action='store_true',
|
||||||
help="when changing (small) files and templates, show the differences in those files; works great with --check")
|
help="when changing (small) files and templates, show the differences in those files; works great with --check")
|
||||||
|
|
||||||
return self.parser
|
|
||||||
|
|
||||||
def post_process_args(self, options, args):
|
def post_process_args(self, options, args):
|
||||||
options, args = super(PullCLI, self).post_process_args(options, args)
|
options, args = super(PullCLI, self).post_process_args(options, args)
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import sys
|
||||||
|
|
||||||
from ansible import constants as C
|
from ansible import constants as C
|
||||||
from ansible import context
|
from ansible import context
|
||||||
|
from ansible.arguments import optparse_helpers as opt_help
|
||||||
from ansible.cli import CLI
|
from ansible.cli import CLI
|
||||||
from ansible.errors import AnsibleOptionsError
|
from ansible.errors import AnsibleOptionsError
|
||||||
from ansible.module_utils._text import to_text, to_bytes
|
from ansible.module_utils._text import to_text, to_bytes
|
||||||
|
@ -62,7 +63,7 @@ class VaultCLI(CLI):
|
||||||
if self.action in self.can_output:
|
if self.action in self.can_output:
|
||||||
self.parser.add_option('--output', default=None, dest='output_file',
|
self.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',
|
||||||
action="callback", callback=self.unfrack_path, type='string')
|
action="callback", callback=opt_help.unfrack_path, type='string')
|
||||||
|
|
||||||
# options specific to self.actions
|
# options specific to self.actions
|
||||||
if self.action == "create":
|
if self.action == "create":
|
||||||
|
@ -97,19 +98,16 @@ class VaultCLI(CLI):
|
||||||
help='the vault id used to encrypt (required if more than vault-id is provided)')
|
help='the vault id used to encrypt (required if more than vault-id is provided)')
|
||||||
|
|
||||||
def init_parser(self):
|
def init_parser(self):
|
||||||
|
super(VaultCLI, self).init_parser(
|
||||||
self.parser = super(VaultCLI, self).init_parser(
|
|
||||||
vault_opts=True,
|
|
||||||
vault_rekey_opts=True,
|
|
||||||
usage="usage: %%prog [%s] [options] [vaultfile.yml]" % "|".join(sorted(self.VALID_ACTIONS)),
|
usage="usage: %%prog [%s] [options] [vaultfile.yml]" % "|".join(sorted(self.VALID_ACTIONS)),
|
||||||
desc="encryption/decryption utility for Ansible data files",
|
desc="encryption/decryption utility for Ansible data files",
|
||||||
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])
|
||||||
)
|
)
|
||||||
|
opt_help.add_vault_options(self.parser)
|
||||||
|
opt_help.add_vault_rekey_options(self.parser)
|
||||||
|
|
||||||
self.set_action()
|
self.set_action()
|
||||||
|
|
||||||
return self.parser
|
|
||||||
|
|
||||||
def post_process_args(self, options, args):
|
def post_process_args(self, options, args):
|
||||||
options, args = super(VaultCLI, self).post_process_args(options, args)
|
options, args = super(VaultCLI, self).post_process_args(options, args)
|
||||||
self.validate_conflicts(options, vault_opts=True, vault_rekey_opts=True)
|
self.validate_conflicts(options, vault_opts=True, vault_rekey_opts=True)
|
||||||
|
|
|
@ -15,39 +15,15 @@ running the ansible command line tools.
|
||||||
These APIs are still in flux so do not use them unless you are willing to update them with every Ansible release
|
These APIs are still in flux so do not use them unless you are willing to update them with every Ansible release
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from ansible import arguments
|
from ansible.arguments.context_objects import CLIArgs, GlobalCLIArgs
|
||||||
|
|
||||||
|
|
||||||
# Note: this is not the singleton version. That is only created once the program has actually
|
# Note: this is not the singleton version. The Singleton is only created once the program has
|
||||||
# parsed the args
|
# actually parsed the args
|
||||||
CLIARGS = arguments.CLIArgs({})
|
CLIARGS = CLIArgs({})
|
||||||
|
|
||||||
|
|
||||||
class _Context:
|
|
||||||
"""
|
|
||||||
Not yet ready for Prime Time
|
|
||||||
|
|
||||||
Eventually this may allow for code which needs to run under different contexts (for instance, as
|
|
||||||
if they were run with different command line args or from different current working directories)
|
|
||||||
to exist in the same process. But at the moment, we don't need that so this code has not been
|
|
||||||
tested for suitability.
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
global CLIARGS
|
|
||||||
self._CLIARGS = arguments.CLIArgs(CLIARGS)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def CLIARGS(self):
|
|
||||||
return self._CLIARGS
|
|
||||||
|
|
||||||
@CLIARGS.setter
|
|
||||||
def CLIARGS_set(self, new_cli_args):
|
|
||||||
if not isinstance(new_cli_args, arguments.CLIArgs):
|
|
||||||
raise TypeError('CLIARGS must be of type (ansible.arguments.CLIArgs)')
|
|
||||||
self._CLIARGS = new_cli_args
|
|
||||||
|
|
||||||
|
|
||||||
def _init_global_context(cli_args):
|
def _init_global_context(cli_args):
|
||||||
"""Initialize the global context objects"""
|
"""Initialize the global context objects"""
|
||||||
global CLIARGS
|
global CLIARGS
|
||||||
CLIARGS = arguments.GlobalCLIArgs.from_options(cli_args)
|
CLIARGS = GlobalCLIArgs.from_options(cli_args)
|
||||||
|
|
|
@ -15,24 +15,25 @@ import optparse
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from ansible import arguments
|
from ansible.arguments import context_objects as co
|
||||||
|
from ansible.module_utils.common.collections import ImmutableDict
|
||||||
|
|
||||||
|
|
||||||
MAKE_IMMUTABLE_DATA = ((u'くらとみ', u'くらとみ'),
|
MAKE_IMMUTABLE_DATA = ((u'くらとみ', u'くらとみ'),
|
||||||
(42, 42),
|
(42, 42),
|
||||||
({u'café': u'くらとみ'}, arguments.ImmutableDict({u'café': u'くらとみ'})),
|
({u'café': u'くらとみ'}, ImmutableDict({u'café': u'くらとみ'})),
|
||||||
([1, u'café', u'くらとみ'], (1, u'café', u'くらとみ')),
|
([1, u'café', u'くらとみ'], (1, u'café', u'くらとみ')),
|
||||||
(set((1, u'café', u'くらとみ')), frozenset((1, u'café', u'くらとみ'))),
|
(set((1, u'café', u'くらとみ')), frozenset((1, u'café', u'くらとみ'))),
|
||||||
({u'café': [1, set(u'ñ')]},
|
({u'café': [1, set(u'ñ')]},
|
||||||
arguments.ImmutableDict({u'café': (1, frozenset(u'ñ'))})),
|
ImmutableDict({u'café': (1, frozenset(u'ñ'))})),
|
||||||
([set((1, 2)), {u'くらとみ': 3}],
|
([set((1, 2)), {u'くらとみ': 3}],
|
||||||
(frozenset((1, 2)), arguments.ImmutableDict({u'くらとみ': 3}))),
|
(frozenset((1, 2)), ImmutableDict({u'くらとみ': 3}))),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('data, expected', MAKE_IMMUTABLE_DATA)
|
@pytest.mark.parametrize('data, expected', MAKE_IMMUTABLE_DATA)
|
||||||
def test_make_immutable(data, expected):
|
def test_make_immutable(data, expected):
|
||||||
assert arguments._make_immutable(data) == expected
|
assert co._make_immutable(data) == expected
|
||||||
|
|
||||||
|
|
||||||
def test_cliargs_from_dict():
|
def test_cliargs_from_dict():
|
||||||
|
@ -43,7 +44,7 @@ def test_cliargs_from_dict():
|
||||||
('check_mode', True),
|
('check_mode', True),
|
||||||
('start_at_task', u'Start with くらとみ')))
|
('start_at_task', u'Start with くらとみ')))
|
||||||
|
|
||||||
assert frozenset(arguments.CLIArgs(old_dict).items()) == expected
|
assert frozenset(co.CLIArgs(old_dict).items()) == expected
|
||||||
|
|
||||||
|
|
||||||
def test_cliargs():
|
def test_cliargs():
|
||||||
|
@ -58,7 +59,7 @@ def test_cliargs():
|
||||||
('check_mode', True),
|
('check_mode', True),
|
||||||
('start_at_task', u'Start with くらとみ')))
|
('start_at_task', u'Start with くらとみ')))
|
||||||
|
|
||||||
assert frozenset(arguments.CLIArgs.from_options(options).items()) == expected
|
assert frozenset(co.CLIArgs.from_options(options).items()) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipIf(argparse is None)
|
@pytest.mark.skipIf(argparse is None)
|
||||||
|
@ -73,7 +74,7 @@ def test_cliargs_argparse():
|
||||||
|
|
||||||
expected = frozenset((('accumulate', sum), ('integers', (1, 2))))
|
expected = frozenset((('accumulate', sum), ('integers', (1, 2))))
|
||||||
|
|
||||||
assert frozenset(arguments.CLIArgs.from_options(args).items()) == expected
|
assert frozenset(co.CLIArgs.from_options(args).items()) == expected
|
||||||
|
|
||||||
|
|
||||||
# Can get rid of this test when we port ansible.cli from optparse to argparse
|
# Can get rid of this test when we port ansible.cli from optparse to argparse
|
||||||
|
@ -87,4 +88,4 @@ def test_cliargs_optparse():
|
||||||
|
|
||||||
expected = frozenset((('accumulate', sum), ('integers', (u'1', u'2'))))
|
expected = frozenset((('accumulate', sum), ('integers', (u'1', u'2'))))
|
||||||
|
|
||||||
assert frozenset(arguments.CLIArgs.from_options(opts).items()) == expected
|
assert frozenset(co.CLIArgs.from_options(opts).items()) == expected
|
19
test/units/arguments/test_optparse_helpers.py
Normal file
19
test/units/arguments/test_optparse_helpers.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright: (c) 2018, Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
# Make coding more python3-ish
|
||||||
|
from __future__ import (absolute_import, division)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from ansible.arguments import optparse_helpers as opt_help
|
||||||
|
|
||||||
|
|
||||||
|
class TestOptparseHelpersVersion:
|
||||||
|
|
||||||
|
def test_version(self):
|
||||||
|
ver = opt_help.version('ansible-cli-test')
|
||||||
|
assert 'ansible-cli-test' in ver
|
||||||
|
assert 'python version' in ver
|
|
@ -31,11 +31,6 @@ from ansible import cli
|
||||||
|
|
||||||
class TestCliVersion(unittest.TestCase):
|
class TestCliVersion(unittest.TestCase):
|
||||||
|
|
||||||
def test_version(self):
|
|
||||||
ver = cli.CLI.version('ansible-cli-test')
|
|
||||||
self.assertIn('ansible-cli-test', ver)
|
|
||||||
self.assertIn('python version', ver)
|
|
||||||
|
|
||||||
def test_version_info(self):
|
def test_version_info(self):
|
||||||
version_info = cli.CLI.version_info()
|
version_info = cli.CLI.version_info()
|
||||||
self.assertEqual(version_info['string'], __version__)
|
self.assertEqual(version_info['string'], __version__)
|
||||||
|
|
|
@ -26,8 +26,9 @@ import tarfile
|
||||||
import tempfile
|
import tempfile
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from ansible import arguments
|
|
||||||
from ansible import context
|
from ansible import context
|
||||||
|
from ansible.arguments import context_objects as co
|
||||||
|
from ansible.arguments import optparse_helpers as opt_help
|
||||||
from ansible.cli.galaxy import GalaxyCLI
|
from ansible.cli.galaxy import GalaxyCLI
|
||||||
from units.compat import unittest
|
from units.compat import unittest
|
||||||
from units.compat.mock import call, patch
|
from units.compat.mock import call, patch
|
||||||
|
@ -98,12 +99,12 @@ class TestGalaxy(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# Reset the stored command line args
|
# Reset the stored command line args
|
||||||
arguments.GlobalCLIArgs._Singleton__instance = None
|
co.GlobalCLIArgs._Singleton__instance = None
|
||||||
self.default_args = ['ansible-galaxy']
|
self.default_args = ['ansible-galaxy']
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
# Reset the stored command line args
|
# Reset the stored command line args
|
||||||
arguments.GlobalCLIArgs._Singleton__instance = None
|
co.GlobalCLIArgs._Singleton__instance = None
|
||||||
|
|
||||||
def test_init(self):
|
def test_init(self):
|
||||||
galaxy_cli = GalaxyCLI(args=self.default_args)
|
galaxy_cli = GalaxyCLI(args=self.default_args)
|
||||||
|
@ -147,7 +148,7 @@ class TestGalaxy(unittest.TestCase):
|
||||||
# removing role
|
# removing role
|
||||||
# Have to reset the arguments in the context object manually since we're doing the
|
# Have to reset the arguments in the context object manually since we're doing the
|
||||||
# equivalent of running the command line program twice
|
# equivalent of running the command line program twice
|
||||||
arguments.GlobalCLIArgs._Singleton__instance = None
|
co.GlobalCLIArgs._Singleton__instance = None
|
||||||
gc = GalaxyCLI(args=["ansible-galaxy", "remove", role_file, self.role_name])
|
gc = GalaxyCLI(args=["ansible-galaxy", "remove", role_file, self.role_name])
|
||||||
gc.run()
|
gc.run()
|
||||||
|
|
||||||
|
@ -172,11 +173,11 @@ class TestGalaxy(unittest.TestCase):
|
||||||
self.assertTrue(mocked_display.called_once_with("- downloading role 'fake_role_name', owned by "))
|
self.assertTrue(mocked_display.called_once_with("- downloading role 'fake_role_name', owned by "))
|
||||||
|
|
||||||
def run_parse_common(self, galaxycli_obj, action):
|
def run_parse_common(self, galaxycli_obj, action):
|
||||||
with patch.object(ansible.cli.SortedOptParser, "set_usage") as mocked_usage:
|
with patch.object(opt_help.SortedOptParser, "set_usage") as mocked_usage:
|
||||||
galaxycli_obj.parse()
|
galaxycli_obj.parse()
|
||||||
|
|
||||||
# checking that the common results of parse() for all possible actions have been created/called
|
# checking that the common results of parse() for all possible actions have been created/called
|
||||||
self.assertIsInstance(galaxycli_obj.parser, ansible.cli.SortedOptParser)
|
self.assertIsInstance(galaxycli_obj.parser, opt_help.SortedOptParser)
|
||||||
formatted_call = {
|
formatted_call = {
|
||||||
'import': 'usage: %prog import [options] github_user github_repo',
|
'import': 'usage: %prog import [options] github_user github_repo',
|
||||||
'delete': 'usage: %prog delete [options] github_user github_repo',
|
'delete': 'usage: %prog delete [options] github_user github_repo',
|
||||||
|
|
|
@ -22,7 +22,7 @@ __metaclass__ = type
|
||||||
from units.compat import unittest
|
from units.compat import unittest
|
||||||
from units.compat.mock import MagicMock
|
from units.compat.mock import MagicMock
|
||||||
|
|
||||||
from ansible import arguments
|
from ansible.arguments import context_objects as co
|
||||||
from ansible.executor.playbook_executor import PlaybookExecutor
|
from ansible.executor.playbook_executor import PlaybookExecutor
|
||||||
from ansible.playbook import Playbook
|
from ansible.playbook import Playbook
|
||||||
from ansible.template import Templar
|
from ansible.template import Templar
|
||||||
|
@ -34,11 +34,11 @@ class TestPlaybookExecutor(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# Reset command line args for every test
|
# Reset command line args for every test
|
||||||
arguments.CLIArgs._Singleton__instance = None
|
co.GlobalCLIArgs._Singleton__instance = None
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
# And cleanup after ourselves too
|
# And cleanup after ourselves too
|
||||||
arguments.CLIArgs._Singleton__instance = None
|
co.GlobalCLIArgs._Singleton__instance = None
|
||||||
|
|
||||||
def test_get_serialized_batches(self):
|
def test_get_serialized_batches(self):
|
||||||
fake_loader = DictDataLoader({
|
fake_loader = DictDataLoader({
|
||||||
|
|
|
@ -21,8 +21,8 @@ from __future__ import (absolute_import, division, print_function)
|
||||||
from units.compat import unittest
|
from units.compat import unittest
|
||||||
from units.compat.mock import MagicMock
|
from units.compat.mock import MagicMock
|
||||||
|
|
||||||
from ansible import arguments
|
|
||||||
from ansible import context
|
from ansible import context
|
||||||
|
from ansible.arguments import context_objects as co
|
||||||
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.plugins.callback import CallbackBase
|
from ansible.plugins.callback import CallbackBase
|
||||||
|
@ -38,7 +38,7 @@ class TestTaskQueueManagerCallbacks(unittest.TestCase):
|
||||||
passwords = []
|
passwords = []
|
||||||
|
|
||||||
# Reset the stored command line args
|
# Reset the stored command line args
|
||||||
arguments.GlobalCLIArgs._Singleton__instance = None
|
co.GlobalCLIArgs._Singleton__instance = None
|
||||||
self._tqm = TaskQueueManager(inventory, variable_manager, loader, passwords)
|
self._tqm = TaskQueueManager(inventory, variable_manager, loader, passwords)
|
||||||
self._playbook = Playbook(loader)
|
self._playbook = Playbook(loader)
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ class TestTaskQueueManagerCallbacks(unittest.TestCase):
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
# Reset the stored command line args
|
# Reset the stored command line args
|
||||||
arguments.GlobalCLIArgs._Singleton__instance = None
|
co.GlobalCLIArgs._Singleton__instance = None
|
||||||
|
|
||||||
def test_task_queue_manager_callbacks_v2_playbook_on_start(self):
|
def test_task_queue_manager_callbacks_v2_playbook_on_start(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -11,10 +11,10 @@ import os
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from ansible import arguments
|
|
||||||
from ansible import constants as C
|
from ansible import constants as C
|
||||||
from ansible import context
|
from ansible import context
|
||||||
from ansible import cli
|
from ansible.arguments import context_objects as co
|
||||||
|
from ansible.arguments import optparse_helpers as opt_help
|
||||||
from units.compat import unittest
|
from units.compat import unittest
|
||||||
from ansible.errors import AnsibleError, AnsibleParserError
|
from ansible.errors import AnsibleError, AnsibleParserError
|
||||||
from ansible.module_utils.six.moves import shlex_quote
|
from ansible.module_utils.six.moves import shlex_quote
|
||||||
|
@ -25,19 +25,26 @@ from units.mock.loader import DictDataLoader
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def parser():
|
def parser():
|
||||||
parser = cli.base_parser(runas_opts=True, meta_opts=True,
|
parser = opt_help.create_base_parser()
|
||||||
runtask_opts=True, vault_opts=True,
|
|
||||||
async_opts=True, connect_opts=True,
|
opt_help.add_runas_options(parser)
|
||||||
subset_opts=True, check_opts=True,
|
opt_help.add_meta_options(parser)
|
||||||
inventory_opts=True,)
|
opt_help.add_runtask_options(parser)
|
||||||
|
opt_help.add_vault_options(parser)
|
||||||
|
opt_help.add_async_options(parser)
|
||||||
|
opt_help.add_connect_options(parser)
|
||||||
|
opt_help.add_subset_options(parser)
|
||||||
|
opt_help.add_check_options(parser)
|
||||||
|
opt_help.add_inventory_options(parser)
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def reset_cli_args():
|
def reset_cli_args():
|
||||||
arguments.GlobalCLIArgs._Singleton__instance = None
|
co.GlobalCLIArgs._Singleton__instance = None
|
||||||
yield
|
yield
|
||||||
arguments.GlobalCLIArgs._Singleton__instance = None
|
co.GlobalCLIArgs._Singleton__instance = None
|
||||||
|
|
||||||
|
|
||||||
def test_play_context(mocker, parser, reset_cli_args):
|
def test_play_context(mocker, parser, reset_cli_args):
|
||||||
|
|
Loading…
Reference in a new issue