mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-04-28 13:21:25 -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
|
@ -8,7 +8,6 @@ __metaclass__ = type
|
|||
import os.path
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import time
|
||||
import yaml
|
||||
|
||||
|
@ -17,7 +16,7 @@ from jinja2 import Environment, FileSystemLoader
|
|||
import ansible.constants as C
|
||||
from ansible import context
|
||||
from ansible.cli import CLI
|
||||
from ansible.cli.arguments import optparse_helpers as opt_help
|
||||
from ansible.cli.arguments import option_helpers as opt_help
|
||||
from ansible.errors import AnsibleError, AnsibleOptionsError
|
||||
from ansible.galaxy import Galaxy
|
||||
from ansible.galaxy.api import GalaxyAPI
|
||||
|
@ -35,109 +34,129 @@ class GalaxyCLI(CLI):
|
|||
'''command to manage Ansible roles in shared repositories, the default of which is Ansible Galaxy *https://galaxy.ansible.com*.'''
|
||||
|
||||
SKIP_INFO_KEYS = ("name", "description", "readme_html", "related", "summary_fields", "average_aw_composite", "average_aw_score", "url")
|
||||
VALID_ACTIONS = frozenset(("delete", "import", "info", "init", "install", "list", "login", "remove", "search", "setup"))
|
||||
|
||||
def __init__(self, args):
|
||||
self.api = None
|
||||
self.galaxy = None
|
||||
super(GalaxyCLI, self).__init__(args)
|
||||
|
||||
def set_action(self):
|
||||
|
||||
super(GalaxyCLI, self).set_action()
|
||||
|
||||
# specific to actions
|
||||
if self.action == "delete":
|
||||
self.parser.set_usage("usage: %prog delete [options] github_user github_repo")
|
||||
self.parser.set_description("Removes the role from Galaxy. It does not remove or alter the actual GitHub repository.")
|
||||
elif self.action == "import":
|
||||
self.parser.set_usage("usage: %prog import [options] github_user github_repo")
|
||||
self.parser.set_description("Import a role.")
|
||||
self.parser.add_option('--no-wait', dest='wait', action='store_false', default=True, help='Don\'t wait for import results.')
|
||||
self.parser.add_option('--branch', dest='reference',
|
||||
help='The name of a branch to import. Defaults to the repository\'s default branch (usually master)')
|
||||
self.parser.add_option('--role-name', dest='role_name', help='The name the role should have, if different than the repo name')
|
||||
self.parser.add_option('--status', dest='check_status', action='store_true', default=False,
|
||||
help='Check the status of the most recent import request for given github_user/github_repo.')
|
||||
elif self.action == "info":
|
||||
self.parser.set_usage("usage: %prog info [options] role_name[,version]")
|
||||
self.parser.set_description("View more details about a specific role.")
|
||||
elif self.action == "init":
|
||||
self.parser.set_usage("usage: %prog init [options] role_name")
|
||||
self.parser.set_description("Initialize new role with the base structure of a role.")
|
||||
self.parser.add_option('--init-path', dest='init_path', default="./",
|
||||
help='The path in which the skeleton role will be created. The default is the current working directory.')
|
||||
self.parser.add_option('--type', dest='role_type', action='store', default='default',
|
||||
help="Initialize using an alternate role type. Valid types include: 'container', 'apb' and 'network'.")
|
||||
self.parser.add_option('--role-skeleton', dest='role_skeleton', default=C.GALAXY_ROLE_SKELETON,
|
||||
help='The path to a role skeleton that the new role should be based upon.')
|
||||
elif self.action == "install":
|
||||
self.parser.set_usage("usage: %prog install [options] [-r FILE | role_name(s)[,version] | scm+role_repo_url[,version] | tar_file(s)]")
|
||||
self.parser.set_description("Install Roles from file(s), URL(s) or tar file(s)")
|
||||
self.parser.add_option('-i', '--ignore-errors', dest='ignore_errors', action='store_true', default=False,
|
||||
help='Ignore errors and continue with the next specified role.')
|
||||
self.parser.add_option('-n', '--no-deps', dest='no_deps', action='store_true', default=False, help='Don\'t download roles listed as dependencies')
|
||||
self.parser.add_option('-r', '--role-file', dest='role_file', help='A file containing a list of roles to be imported')
|
||||
self.parser.add_option('-g', '--keep-scm-meta', dest='keep_scm_meta', action='store_true',
|
||||
default=False, help='Use tar instead of the scm archive option when packaging the role')
|
||||
elif self.action == "remove":
|
||||
self.parser.set_usage("usage: %prog remove role1 role2 ...")
|
||||
self.parser.set_description("Delete a role from roles_path.")
|
||||
elif self.action == "list":
|
||||
self.parser.set_usage("usage: %prog list [role_name]")
|
||||
self.parser.set_description("Show the name and version of each role installed in the roles_path.")
|
||||
elif self.action == "login":
|
||||
self.parser.set_usage("usage: %prog login [options]")
|
||||
self.parser.set_description("Login to api.github.com server in order to use ansible-galaxy sub command such as 'import', 'delete' and 'setup'.")
|
||||
self.parser.add_option('--github-token', dest='token', default=None, help='Identify with github token rather than username and password.')
|
||||
elif self.action == "search":
|
||||
self.parser.set_usage("usage: %prog search [searchterm1 searchterm2] [--galaxy-tags galaxy_tag1,galaxy_tag2] [--platforms platform1,platform2] "
|
||||
"[--author username]")
|
||||
self.parser.add_option('--platforms', dest='platforms', help='list of OS platforms to filter by')
|
||||
self.parser.add_option('--galaxy-tags', dest='galaxy_tags', help='list of galaxy tags to filter by')
|
||||
self.parser.add_option('--author', dest='author', help='GitHub username')
|
||||
self.parser.set_description("Search the Galaxy database by tags, platforms, author and multiple keywords.")
|
||||
elif self.action == "setup":
|
||||
self.parser.set_usage("usage: %prog setup [options] source github_user github_repo secret")
|
||||
self.parser.add_option('--remove', dest='remove_id', default=None,
|
||||
help='Remove the integration matching the provided ID value. Use --list to see ID values.')
|
||||
self.parser.add_option('--list', dest="setup_list", action='store_true', default=False, help='List all of your integrations.')
|
||||
self.parser.set_description("Manage the integration between Galaxy and the given source.")
|
||||
# options that apply to more than one action
|
||||
if self.action in ['init', 'info']:
|
||||
self.parser.add_option('--offline', dest='offline', default=False, action='store_true', help="Don't query the galaxy API when creating roles")
|
||||
|
||||
if self.action not in ("delete", "import", "init", "login", "setup"):
|
||||
# NOTE: while the option type=str, the default is a list, and the
|
||||
# callback will set the value to a list.
|
||||
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'
|
||||
' file (/etc/ansible/roles if not configured)', type='str')
|
||||
if self.action in ("init", "install"):
|
||||
self.parser.add_option('-f', '--force', dest='force', action='store_true', default=False, help='Force overwriting an existing role')
|
||||
if self.action == "install":
|
||||
self.parser.add_option('--force-with-deps', dest='force_with_deps', action='store_true', default=False,
|
||||
help="Force overwriting an existing role and it's dependencies")
|
||||
|
||||
def init_parser(self):
|
||||
''' create an options parser for bin/ansible '''
|
||||
|
||||
super(GalaxyCLI, self).init_parser(
|
||||
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]),
|
||||
desc="Perform various Role related operations.",
|
||||
)
|
||||
|
||||
# common
|
||||
self.parser.add_option('-s', '--server', dest='api_server', default=C.GALAXY_SERVER, help='The API server destination')
|
||||
self.parser.add_option('-c', '--ignore-certs', action='store_true', dest='ignore_certs', default=C.GALAXY_IGNORE_CERTS,
|
||||
help='Ignore SSL certificate validation errors.')
|
||||
self.set_action()
|
||||
common = opt_help.argparse.ArgumentParser(add_help=False)
|
||||
common.add_argument('-s', '--server', dest='api_server', default=C.GALAXY_SERVER, help='The API server destination')
|
||||
common.add_argument('-c', '--ignore-certs', action='store_true', dest='ignore_certs', default=C.GALAXY_IGNORE_CERTS,
|
||||
help='Ignore SSL certificate validation errors.')
|
||||
opt_help.add_verbosity_options(common)
|
||||
|
||||
def post_process_args(self, options, args):
|
||||
options, args = super(GalaxyCLI, self).post_process_args(options, args)
|
||||
# options that apply to more than one action
|
||||
user_repo = opt_help.argparse.ArgumentParser(add_help=False)
|
||||
user_repo.add_argument('github_user', help='GitHub username')
|
||||
user_repo.add_argument('github_repo', help='GitHub repository')
|
||||
|
||||
offline = opt_help.argparse.ArgumentParser(add_help=False)
|
||||
offline.add_argument('--offline', dest='offline', default=False, action='store_true',
|
||||
help="Don't query the galaxy API when creating roles")
|
||||
|
||||
roles_path = opt_help.argparse.ArgumentParser(add_help=False)
|
||||
roles_path.add_argument('-p', '--roles-path', dest='roles_path', type=opt_help.unfrack_path(pathsep=True),
|
||||
default=C.DEFAULT_ROLES_PATH, action=opt_help.PrependListAction,
|
||||
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)')
|
||||
|
||||
force = opt_help.argparse.ArgumentParser(add_help=False)
|
||||
force.add_argument('-f', '--force', dest='force', action='store_true', default=False, help='Force overwriting an existing role')
|
||||
|
||||
subparsers = self.parser.add_subparsers(dest='action')
|
||||
subparsers.required = True
|
||||
|
||||
delete_parser = subparsers.add_parser('delete', parents=[user_repo, common],
|
||||
help='Removes the role from Galaxy. It does not remove or alter the actual GitHub repository.')
|
||||
delete_parser.set_defaults(func=self.execute_delete)
|
||||
|
||||
import_parser = subparsers.add_parser('import', help='Import a role', parents=[user_repo, common])
|
||||
import_parser.set_defaults(func=self.execute_import)
|
||||
import_parser.add_argument('--no-wait', dest='wait', action='store_false', default=True, help="Don't wait for import results.")
|
||||
import_parser.add_argument('--branch', dest='reference',
|
||||
help='The name of a branch to import. Defaults to the repository\'s default branch (usually master)')
|
||||
import_parser.add_argument('--role-name', dest='role_name', help='The name the role should have, if different than the repo name')
|
||||
import_parser.add_argument('--status', dest='check_status', action='store_true', default=False,
|
||||
help='Check the status of the most recent import request for given github_user/github_repo.')
|
||||
|
||||
info_parser = subparsers.add_parser('info', help='View more details about a specific role.',
|
||||
parents=[offline, common, roles_path])
|
||||
info_parser.set_defaults(func=self.execute_info)
|
||||
info_parser.add_argument('args', nargs='+', help='role', metavar='role_name[,version]')
|
||||
|
||||
init_parser = subparsers.add_parser('init', help='Initialize new role with the base structure of a role.',
|
||||
parents=[offline, force, common])
|
||||
init_parser.set_defaults(func=self.execute_init)
|
||||
init_parser.add_argument('--init-path', dest='init_path', default="./",
|
||||
help='The path in which the skeleton role will be created. The default is the current working directory.')
|
||||
init_parser.add_argument('--type', dest='role_type', action='store', default='default',
|
||||
help="Initialize using an alternate role type. Valid types include: 'container', 'apb' and 'network'.")
|
||||
init_parser.add_argument('--role-skeleton', dest='role_skeleton', default=C.GALAXY_ROLE_SKELETON,
|
||||
help='The path to a role skeleton that the new role should be based upon.')
|
||||
init_parser.add_argument('role_name', help='Role name')
|
||||
|
||||
install_parser = subparsers.add_parser('install', help='Install Roles from file(s), URL(s) or tar file(s)',
|
||||
parents=[force, common, roles_path])
|
||||
install_parser.set_defaults(func=self.execute_install)
|
||||
install_parser.add_argument('-i', '--ignore-errors', dest='ignore_errors', action='store_true', default=False,
|
||||
help='Ignore errors and continue with the next specified role.')
|
||||
install_parser.add_argument('-r', '--role-file', dest='role_file', help='A file containing a list of roles to be imported')
|
||||
install_parser.add_argument('-g', '--keep-scm-meta', dest='keep_scm_meta', action='store_true',
|
||||
default=False, help='Use tar instead of the scm archive option when packaging the role')
|
||||
install_parser.add_argument('args', help='Role name, URL or tar file', metavar='role', nargs='*')
|
||||
install_exclusive = install_parser.add_mutually_exclusive_group()
|
||||
install_exclusive.add_argument('-n', '--no-deps', dest='no_deps', action='store_true', default=False,
|
||||
help="Don't download roles listed as dependencies")
|
||||
install_exclusive.add_argument('--force-with-deps', dest='force_with_deps', action='store_true', default=False,
|
||||
help="Force overwriting an existing role and it's dependencies")
|
||||
|
||||
remove_parser = subparsers.add_parser('remove', help='Delete roles from roles_path.', parents=[common, roles_path])
|
||||
remove_parser.set_defaults(func=self.execute_remove)
|
||||
remove_parser.add_argument('args', help='Role(s)', metavar='role', nargs='+')
|
||||
|
||||
list_parser = subparsers.add_parser('list', help='Show the name and version of each role installed in the roles_path.',
|
||||
parents=[common, roles_path])
|
||||
list_parser.set_defaults(func=self.execute_list)
|
||||
list_parser.add_argument('role', help='Role', nargs='?', metavar='role')
|
||||
|
||||
login_parser = subparsers.add_parser('login', parents=[common],
|
||||
help="Login to api.github.com server in order to use ansible-galaxy sub "
|
||||
"command such as 'import', 'delete' and 'setup'")
|
||||
login_parser.set_defaults(func=self.execute_login)
|
||||
login_parser.add_argument('--github-token', dest='token', default=None, help='Identify with github token rather than username and password.')
|
||||
|
||||
search_parser = subparsers.add_parser('search', help='Search the Galaxy database by tags, platforms, author and multiple keywords.',
|
||||
parents=[common])
|
||||
search_parser.set_defaults(func=self.execute_search)
|
||||
search_parser.add_argument('--platforms', dest='platforms', help='list of OS platforms to filter by')
|
||||
search_parser.add_argument('--galaxy-tags', dest='galaxy_tags', help='list of galaxy tags to filter by')
|
||||
search_parser.add_argument('--author', dest='author', help='GitHub username')
|
||||
search_parser.add_argument('args', help='Search terms', metavar='searchterm', nargs='*')
|
||||
|
||||
setup_parser = subparsers.add_parser('setup', help='Manage the integration between Galaxy and the given source.',
|
||||
parents=[roles_path, common])
|
||||
setup_parser.set_defaults(func=self.execute_setup)
|
||||
setup_parser.add_argument('--remove', dest='remove_id', default=None,
|
||||
help='Remove the integration matching the provided ID value. Use --list to see ID values.')
|
||||
setup_parser.add_argument('--list', dest="setup_list", action='store_true', default=False, help='List all of your integrations.')
|
||||
setup_parser.add_argument('source', help='Source')
|
||||
setup_parser.add_argument('github_user', help='GitHub username')
|
||||
setup_parser.add_argument('github_repo', help='GitHub repository')
|
||||
setup_parser.add_argument('secret', help='Secret')
|
||||
|
||||
def post_process_args(self, options):
|
||||
options = super(GalaxyCLI, self).post_process_args(options)
|
||||
display.verbosity = options.verbosity
|
||||
return options, args
|
||||
return options
|
||||
|
||||
def run(self):
|
||||
|
||||
|
@ -146,7 +165,7 @@ class GalaxyCLI(CLI):
|
|||
self.galaxy = Galaxy()
|
||||
|
||||
self.api = GalaxyAPI(self.galaxy)
|
||||
self.execute()
|
||||
context.CLIARGS['func']()
|
||||
|
||||
@staticmethod
|
||||
def exit_without_ignore(rc=1):
|
||||
|
@ -192,9 +211,7 @@ class GalaxyCLI(CLI):
|
|||
force = context.CLIARGS['force']
|
||||
role_skeleton = context.CLIARGS['role_skeleton']
|
||||
|
||||
role_name = context.CLIARGS['args'][0].strip() if context.CLIARGS['args'] else None
|
||||
if not role_name:
|
||||
raise AnsibleOptionsError("- no role name specified for init")
|
||||
role_name = context.CLIARGS['role_name']
|
||||
role_path = os.path.join(init_path, role_name)
|
||||
if os.path.exists(role_path):
|
||||
if os.path.isfile(role_path):
|
||||
|
@ -260,10 +277,6 @@ class GalaxyCLI(CLI):
|
|||
prints out detailed information about an installed role as well as info available from the galaxy API.
|
||||
"""
|
||||
|
||||
if not context.CLIARGS['args']:
|
||||
# the user needs to specify a role
|
||||
raise AnsibleOptionsError("- you must specify a user/role name")
|
||||
|
||||
roles_path = context.CLIARGS['roles_path']
|
||||
|
||||
data = ''
|
||||
|
@ -316,9 +329,6 @@ class GalaxyCLI(CLI):
|
|||
no_deps = context.CLIARGS['no_deps']
|
||||
force_deps = context.CLIARGS['force_with_deps']
|
||||
|
||||
if no_deps and force_deps:
|
||||
raise AnsibleOptionsError("You cannot both force dependencies and no dependencies")
|
||||
|
||||
force = context.CLIARGS['force'] or force_deps
|
||||
|
||||
roles_left = []
|
||||
|
@ -329,7 +339,9 @@ class GalaxyCLI(CLI):
|
|||
try:
|
||||
required_roles = yaml.safe_load(f.read())
|
||||
except Exception as e:
|
||||
raise AnsibleError("Unable to load data from the requirements file (%s): %s" % (role_file, to_native(e)))
|
||||
raise AnsibleError(
|
||||
"Unable to load data from the requirements file (%s): %s" % (role_file, to_native(e))
|
||||
)
|
||||
|
||||
if required_roles is None:
|
||||
raise AnsibleError("No roles found in file: %s" % role_file)
|
||||
|
@ -463,9 +475,6 @@ class GalaxyCLI(CLI):
|
|||
lists the roles installed on the local system or matches a single role passed as an argument.
|
||||
"""
|
||||
|
||||
if len(context.CLIARGS['args']) > 1:
|
||||
raise AnsibleOptionsError("- please specify only one role to list, or specify no roles to see a full list")
|
||||
|
||||
def _display_role(gr):
|
||||
install_info = gr.install_info
|
||||
version = None
|
||||
|
@ -475,9 +484,9 @@ class GalaxyCLI(CLI):
|
|||
version = "(unknown version)"
|
||||
display.display("- %s, %s" % (gr.name, version))
|
||||
|
||||
if context.CLIARGS['args']:
|
||||
if context.CLIARGS['role']:
|
||||
# show the requested role, if it exists
|
||||
name = context.CLIARGS['args'][0]
|
||||
name = context.CLIARGS['role']
|
||||
gr = GalaxyRole(self.galaxy, name)
|
||||
if gr.metadata:
|
||||
display.display('# %s' % os.path.dirname(gr.path))
|
||||
|
@ -553,7 +562,7 @@ class GalaxyCLI(CLI):
|
|||
|
||||
def execute_login(self):
|
||||
"""
|
||||
verify user's identify via GitHub and retrieve an auth token from Ansible Galaxy.
|
||||
verify user's identify via Github and retrieve an auth token from Ansible Galaxy.
|
||||
"""
|
||||
# Authenticate with github and retrieve a token
|
||||
if context.CLIARGS['token'] is None:
|
||||
|
@ -605,7 +614,7 @@ class GalaxyCLI(CLI):
|
|||
|
||||
if len(task) > 1:
|
||||
# found multiple roles associated with github_user/github_repo
|
||||
display.display("WARNING: More than one Galaxy role associated with GitHub repo %s/%s." % (github_user, github_repo),
|
||||
display.display("WARNING: More than one Galaxy role associated with Github repo %s/%s." % (github_user, github_repo),
|
||||
color='yellow')
|
||||
display.display("The following Galaxy roles are being updated:" + u'\n', color=C.COLOR_CHANGED)
|
||||
for t in task:
|
||||
|
@ -637,7 +646,7 @@ class GalaxyCLI(CLI):
|
|||
return 0
|
||||
|
||||
def execute_setup(self):
|
||||
""" Setup an integration from GitHub or Travis for Ansible Galaxy roles"""
|
||||
""" Setup an integration from Github or Travis for Ansible Galaxy roles"""
|
||||
|
||||
if context.CLIARGS['setup_list']:
|
||||
# List existing integration secrets
|
||||
|
@ -659,13 +668,10 @@ class GalaxyCLI(CLI):
|
|||
display.display("Secret removed. Integrations using this secret will not longer work.", color=C.COLOR_OK)
|
||||
return 0
|
||||
|
||||
if len(context.CLIARGS['args']) < 4:
|
||||
raise AnsibleError("Missing one or more arguments. Expecting: source github_user github_repo secret")
|
||||
|
||||
source = context.CLIARGS['args'][0]
|
||||
github_user = context.CLIARGS['args'][1]
|
||||
github_repo = context.CLIARGS['args'][2]
|
||||
secret = context.CLIARGS['args'][3]
|
||||
source = context.CLIARGS['source']
|
||||
github_user = context.CLIARGS['github_user']
|
||||
github_repo = context.CLIARGS['github_repo']
|
||||
secret = context.CLIARGS['secret']
|
||||
|
||||
resp = self.api.add_secret(source, github_user, github_repo, secret)
|
||||
display.display("Added integration for %s %s/%s" % (resp['source'], resp['github_user'], resp['github_repo']))
|
||||
|
@ -675,11 +681,8 @@ class GalaxyCLI(CLI):
|
|||
def execute_delete(self):
|
||||
""" Delete a role from Ansible Galaxy. """
|
||||
|
||||
if len(context.CLIARGS['args']) < 2:
|
||||
raise AnsibleError("Missing one or more arguments. Expected: github_user github_repo")
|
||||
|
||||
github_user = context.CLIARGS['args'][0]
|
||||
github_repo = context.CLIARGS['args'][1]
|
||||
github_user = context.CLIARGS['github_user']
|
||||
github_repo = context.CLIARGS['github_repo']
|
||||
resp = self.api.delete_role(github_user, github_repo)
|
||||
|
||||
if len(resp['deleted_roles']) > 1:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue