mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-05-11 03:31:29 -07:00
Migrate command line parsing to argparse (#50610)
* Start of migration to argparse * various fixes and improvements * Linting fixes * Test fixes * Fix vault_password_files * Add PrependAction for argparse * A bunch of additional tweak/fixes * Fix ansible-config tests * Fix man page generation * linting fix * More adhoc pattern fixes * Add changelog fragment * Add support for argcomplete * Enable argcomplete global completion * Rename PrependAction to PrependListAction to better describe what it does * Add documentation for installing and configuring argcomplete * Address rebase issues * Fix display encoding for vault * Fix line length * Address rebase issues * Handle rebase issues * Use mutually exclusive group instead of handling manually * Fix rebase issues * Address rebase issue * Update version added for argcomplete support * -e must be given a value * ci_complete
This commit is contained in:
parent
7ee6c136fd
commit
db6cc60352
28 changed files with 930 additions and 914 deletions
|
@ -17,8 +17,8 @@ from abc import ABCMeta, abstractmethod
|
|||
|
||||
from ansible import constants as C
|
||||
from ansible import context
|
||||
from ansible.cli.arguments import optparse_helpers as opt_help
|
||||
from ansible.errors import AnsibleOptionsError, AnsibleError
|
||||
from ansible.cli.arguments import option_helpers as opt_help
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.inventory.manager import InventoryManager
|
||||
from ansible.module_utils.six import with_metaclass, string_types
|
||||
from ansible.module_utils._text import to_bytes, to_text
|
||||
|
@ -30,6 +30,12 @@ from ansible.vars.manager import VariableManager
|
|||
from ansible.parsing.vault import PromptVaultSecret, get_file_vault_secret
|
||||
from ansible.plugins.loader import add_all_plugin_dirs
|
||||
|
||||
try:
|
||||
import argcomplete
|
||||
HAS_ARGCOMPLETE = True
|
||||
except ImportError:
|
||||
HAS_ARGCOMPLETE = False
|
||||
|
||||
|
||||
display = Display()
|
||||
|
||||
|
@ -37,8 +43,6 @@ display = Display()
|
|||
class CLI(with_metaclass(ABCMeta, object)):
|
||||
''' code behind bin/ansible* programs '''
|
||||
|
||||
VALID_ACTIONS = frozenset()
|
||||
|
||||
_ITALIC = re.compile(r"I\(([^)]+)\)")
|
||||
_BOLD = re.compile(r"B\(([^)]+)\)")
|
||||
_MODULE = re.compile(r"M\(([^)]+)\)")
|
||||
|
@ -59,38 +63,8 @@ class CLI(with_metaclass(ABCMeta, object)):
|
|||
|
||||
self.args = args
|
||||
self.parser = None
|
||||
self.action = None
|
||||
self.callback = callback
|
||||
|
||||
def set_action(self):
|
||||
"""
|
||||
Get the action the user wants to execute from the sys argv list.
|
||||
"""
|
||||
for i in range(0, len(self.args)):
|
||||
arg = self.args[i]
|
||||
if arg in self.VALID_ACTIONS:
|
||||
self.action = arg
|
||||
del self.args[i]
|
||||
break
|
||||
|
||||
if not self.action:
|
||||
# if we're asked for help or version, we don't need an action.
|
||||
# have to use a special purpose Option Parser to figure that out as
|
||||
# the standard OptionParser throws an error for unknown options and
|
||||
# without knowing action, we only know of a subset of the options
|
||||
# that could be legal for this command
|
||||
tmp_parser = opt_help.InvalidOptsParser(self.parser)
|
||||
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):
|
||||
raise AnsibleOptionsError("Missing required action")
|
||||
|
||||
def execute(self):
|
||||
"""
|
||||
Actually runs a child defined method using the execute_<action> pattern
|
||||
"""
|
||||
fn = getattr(self, "execute_%s" % self.action)
|
||||
fn()
|
||||
|
||||
@abstractmethod
|
||||
def run(self):
|
||||
"""Run the ansible command
|
||||
|
@ -100,7 +74,7 @@ class CLI(with_metaclass(ABCMeta, object)):
|
|||
"""
|
||||
self.parse()
|
||||
|
||||
display.vv(to_text(self.parser.get_version()))
|
||||
display.vv(to_text(opt_help.version(self.parser.prog)))
|
||||
|
||||
if C.CONFIG_FILE:
|
||||
display.v(u"Using %s as config file" % to_text(C.CONFIG_FILE))
|
||||
|
@ -277,18 +251,9 @@ class CLI(with_metaclass(ABCMeta, object)):
|
|||
|
||||
return (sshpass, becomepass)
|
||||
|
||||
def validate_conflicts(self, op, vault_opts=False, runas_opts=False, fork_opts=False, vault_rekey_opts=False):
|
||||
def validate_conflicts(self, op, runas_opts=False, fork_opts=False):
|
||||
''' check for conflicting options '''
|
||||
|
||||
if vault_opts:
|
||||
# Check for vault related conflicts
|
||||
if op.ask_vault_pass and op.vault_password_files:
|
||||
self.parser.error("--ask-vault-pass and --vault-password-file are mutually exclusive")
|
||||
|
||||
if vault_rekey_opts:
|
||||
if op.new_vault_id and op.new_vault_password_file:
|
||||
self.parser.error("--new-vault-password-file and --new-vault-id are mutually exclusive")
|
||||
|
||||
if fork_opts:
|
||||
if op.forks < 1:
|
||||
self.parser.error("The number of processes (--forks) must be >= 1")
|
||||
|
@ -307,13 +272,13 @@ class CLI(with_metaclass(ABCMeta, object)):
|
|||
|
||||
def init_parser(self):
|
||||
super(MyCLI, self).init_parser(usage="My Ansible CLI", inventory_opts=True)
|
||||
ansible.arguments.optparse_helpers.add_runas_options(self.parser)
|
||||
ansible.arguments.option_helpers.add_runas_options(self.parser)
|
||||
self.parser.add_option('--my-option', dest='my_option', action='store')
|
||||
"""
|
||||
self.parser = opt_help.create_base_parser(usage=usage, desc=desc, epilog=epilog)
|
||||
|
||||
@abstractmethod
|
||||
def post_process_args(self, options, args):
|
||||
def post_process_args(self, options):
|
||||
"""Process the command line args
|
||||
|
||||
Subclasses need to implement this method. This method validates and transforms the command
|
||||
|
@ -322,13 +287,13 @@ class CLI(with_metaclass(ABCMeta, object)):
|
|||
|
||||
An implementation will look something like this::
|
||||
|
||||
def post_process_args(self, options, args):
|
||||
options, args = super(MyCLI, self).post_process_args(options, args)
|
||||
def post_process_args(self, options):
|
||||
options = super(MyCLI, self).post_process_args(options)
|
||||
if options.addition and options.subtraction:
|
||||
raise AnsibleOptionsError('Only one of --addition and --subtraction can be specified')
|
||||
if isinstance(options.listofhosts, string_types):
|
||||
options.listofhosts = string_types.split(',')
|
||||
return options, args
|
||||
return options
|
||||
"""
|
||||
|
||||
# process tags
|
||||
|
@ -364,7 +329,7 @@ class CLI(with_metaclass(ABCMeta, object)):
|
|||
else:
|
||||
options.inventory = C.DEFAULT_HOST_LIST
|
||||
|
||||
return options, args
|
||||
return options
|
||||
|
||||
def parse(self):
|
||||
"""Parse the command line args
|
||||
|
@ -377,9 +342,12 @@ class CLI(with_metaclass(ABCMeta, object)):
|
|||
are called from this function before and after parsing the arguments.
|
||||
"""
|
||||
self.init_parser()
|
||||
options, args = self.parser.parse_args(self.args[1:])
|
||||
options, args = self.post_process_args(options, args)
|
||||
options.args = args
|
||||
|
||||
if HAS_ARGCOMPLETE:
|
||||
argcomplete.autocomplete(self.parser)
|
||||
|
||||
options = self.parser.parse_args(self.args[1:])
|
||||
options = self.post_process_args(options)
|
||||
context._init_global_context(options)
|
||||
|
||||
@staticmethod
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue