mirror of
https://github.com/ansible/awx.git
synced 2026-03-19 18:07:33 -02:30
move code linting to a stricter pep8-esque auto-formatting tool, black
This commit is contained in:
@@ -56,14 +56,7 @@ def run(stdout=sys.stdout, stderr=sys.stderr, argv=[]):
|
||||
json.dump(e.msg, sys.stdout)
|
||||
print('')
|
||||
elif cli.get_config('format') == 'yaml':
|
||||
sys.stdout.write(to_str(
|
||||
yaml.safe_dump(
|
||||
e.msg,
|
||||
default_flow_style=False,
|
||||
encoding='utf-8',
|
||||
allow_unicode=True
|
||||
)
|
||||
))
|
||||
sys.stdout.write(to_str(yaml.safe_dump(e.msg, default_flow_style=False, encoding='utf-8', allow_unicode=True)))
|
||||
elif cli.get_config('format') == 'human':
|
||||
sys.stdout.write(e.__class__.__name__)
|
||||
print('')
|
||||
|
||||
@@ -8,9 +8,7 @@ import sys
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
from .custom import handle_custom_actions
|
||||
from .format import (add_authentication_arguments,
|
||||
add_output_formatting_arguments,
|
||||
FORMATTERS, format_response)
|
||||
from .format import add_authentication_arguments, add_output_formatting_arguments, FORMATTERS, format_response
|
||||
from .options import ResourceOptionsParser, UNIQUENESS_RULES
|
||||
from .resource import parse_resource, is_control_resource
|
||||
from awxkit import api, config, utils, exceptions, WSClient # noqa
|
||||
@@ -88,7 +86,9 @@ class CLI(object):
|
||||
token = self.get_config('token')
|
||||
if token:
|
||||
self.root.connection.login(
|
||||
None, None, token=token,
|
||||
None,
|
||||
None,
|
||||
token=token,
|
||||
)
|
||||
else:
|
||||
config.use_sessions = True
|
||||
@@ -102,12 +102,14 @@ class CLI(object):
|
||||
if self.get_config('insecure'):
|
||||
config.assume_untrusted = True
|
||||
|
||||
config.credentials = utils.PseudoNamespace({
|
||||
'default': {
|
||||
'username': self.get_config('username'),
|
||||
'password': self.get_config('password'),
|
||||
config.credentials = utils.PseudoNamespace(
|
||||
{
|
||||
'default': {
|
||||
'username': self.get_config('username'),
|
||||
'password': self.get_config('password'),
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
_, remainder = self.parser.parse_known_args()
|
||||
if remainder and remainder[0] == 'config':
|
||||
@@ -133,11 +135,7 @@ class CLI(object):
|
||||
try:
|
||||
self.v2 = self.root.get().available_versions.v2.get()
|
||||
except AttributeError:
|
||||
raise RuntimeError(
|
||||
'An error occurred while fetching {}/api/'.format(
|
||||
self.get_config('host')
|
||||
)
|
||||
)
|
||||
raise RuntimeError('An error occurred while fetching {}/api/'.format(self.get_config('host')))
|
||||
|
||||
def parse_resource(self, skip_deprecated=False):
|
||||
"""Attempt to parse the <resource> (e.g., jobs) specified on the CLI
|
||||
@@ -170,33 +168,15 @@ class CLI(object):
|
||||
_filter = self.get_config('filter')
|
||||
|
||||
# human format for metrics, settings is special
|
||||
if (
|
||||
self.resource in ('metrics', 'settings') and
|
||||
self.get_config('format') == 'human'
|
||||
):
|
||||
response.json = {
|
||||
'count': len(response.json),
|
||||
'results': [
|
||||
{'key': k, 'value': v}
|
||||
for k, v in response.json.items()
|
||||
]
|
||||
}
|
||||
if self.resource in ('metrics', 'settings') and self.get_config('format') == 'human':
|
||||
response.json = {'count': len(response.json), 'results': [{'key': k, 'value': v} for k, v in response.json.items()]}
|
||||
_filter = 'key, value'
|
||||
|
||||
if (
|
||||
self.get_config('format') == 'human' and
|
||||
_filter == '.' and
|
||||
self.resource in UNIQUENESS_RULES
|
||||
):
|
||||
if self.get_config('format') == 'human' and _filter == '.' and self.resource in UNIQUENESS_RULES:
|
||||
_filter = ', '.join(UNIQUENESS_RULES[self.resource])
|
||||
|
||||
formatted = format_response(
|
||||
response,
|
||||
fmt=self.get_config('format'),
|
||||
filter=_filter,
|
||||
changed=self.original_action in (
|
||||
'modify', 'create', 'associate', 'disassociate'
|
||||
)
|
||||
response, fmt=self.get_config('format'), filter=_filter, changed=self.original_action in ('modify', 'create', 'associate', 'disassociate')
|
||||
)
|
||||
if formatted:
|
||||
print(utils.to_str(formatted), file=self.stdout)
|
||||
@@ -219,10 +199,7 @@ class CLI(object):
|
||||
_without_ triggering a SystemExit (argparse's
|
||||
behavior if required arguments are missing)
|
||||
"""
|
||||
subparsers = self.subparsers[self.resource].add_subparsers(
|
||||
dest='action',
|
||||
metavar='action'
|
||||
)
|
||||
subparsers = self.subparsers[self.resource].add_subparsers(dest='action', metavar='action')
|
||||
subparsers.required = True
|
||||
|
||||
# parse the action from OPTIONS
|
||||
@@ -252,10 +229,7 @@ class CLI(object):
|
||||
if self.resource != 'settings':
|
||||
for method in ('list', 'modify', 'create'):
|
||||
if method in parser.parser.choices:
|
||||
parser.build_query_arguments(
|
||||
method,
|
||||
'GET' if method == 'list' else 'POST'
|
||||
)
|
||||
parser.build_query_arguments(method, 'GET' if method == 'list' else 'POST')
|
||||
if from_sphinx:
|
||||
parsed, extra = self.parser.parse_known_args(self.argv)
|
||||
else:
|
||||
@@ -263,10 +237,7 @@ class CLI(object):
|
||||
|
||||
if extra and self.verbose:
|
||||
# If extraneous arguments were provided, warn the user
|
||||
cprint('{}: unrecognized arguments: {}'.format(
|
||||
self.parser.prog,
|
||||
' '.join(extra)
|
||||
), 'yellow', file=self.stdout)
|
||||
cprint('{}: unrecognized arguments: {}'.format(self.parser.prog, ' '.join(extra)), 'yellow', file=self.stdout)
|
||||
|
||||
# build a dictionary of all of the _valid_ flags specified on the
|
||||
# command line so we can pass them on to the underlying awxkit call
|
||||
@@ -275,14 +246,7 @@ class CLI(object):
|
||||
# everything else is a flag used as a query argument for the HTTP
|
||||
# request we'll make (e.g., --username="Joe", --verbosity=3)
|
||||
parsed = parsed.__dict__
|
||||
parsed = dict(
|
||||
(k, v) for k, v in parsed.items()
|
||||
if (
|
||||
v is not None and
|
||||
k not in ('help', 'resource') and
|
||||
not k.startswith('conf.')
|
||||
)
|
||||
)
|
||||
parsed = dict((k, v) for k, v in parsed.items() if (v is not None and k not in ('help', 'resource') and not k.startswith('conf.')))
|
||||
|
||||
# if `id` is one of the arguments, it's a detail view
|
||||
if 'id' in parsed:
|
||||
@@ -290,9 +254,7 @@ class CLI(object):
|
||||
|
||||
# determine the awxkit method to call
|
||||
action = self.original_action = parsed.pop('action')
|
||||
page, action = handle_custom_actions(
|
||||
self.resource, action, page
|
||||
)
|
||||
page, action = handle_custom_actions(self.resource, action, page)
|
||||
self.method = {
|
||||
'list': 'get',
|
||||
'modify': 'patch',
|
||||
@@ -327,13 +289,7 @@ class CLI(object):
|
||||
action='store_true',
|
||||
help='prints usage information for the awx tool',
|
||||
)
|
||||
self.parser.add_argument(
|
||||
'--version',
|
||||
dest='conf.version',
|
||||
action='version',
|
||||
help='display awx CLI version',
|
||||
version=__version__
|
||||
)
|
||||
self.parser.add_argument('--version', dest='conf.version', action='version', help='display awx CLI version', version=__version__)
|
||||
add_authentication_arguments(self.parser, env)
|
||||
add_output_formatting_arguments(self.parser, env)
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ def handle_custom_actions(resource, action, page):
|
||||
|
||||
|
||||
class CustomActionRegistryMeta(CustomRegistryMeta):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return ' '.join([self.resource, self.action])
|
||||
@@ -45,33 +44,16 @@ class CustomAction(metaclass=CustomActionRegistryMeta):
|
||||
|
||||
|
||||
class Launchable(object):
|
||||
|
||||
def add_arguments(self, parser, resource_options_parser, with_pk=True):
|
||||
from .options import pk_or_name
|
||||
if with_pk:
|
||||
parser.choices[self.action].add_argument(
|
||||
'id',
|
||||
type=functools.partial(
|
||||
pk_or_name, None, self.resource, page=self.page
|
||||
),
|
||||
help=''
|
||||
)
|
||||
parser.choices[self.action].add_argument(
|
||||
'--monitor', action='store_true',
|
||||
help='If set, prints stdout of the launched job until it finishes.'
|
||||
)
|
||||
parser.choices[self.action].add_argument(
|
||||
'--timeout', type=int,
|
||||
help='If set with --monitor or --wait, time out waiting on job completion.' # noqa
|
||||
)
|
||||
parser.choices[self.action].add_argument(
|
||||
'--wait', action='store_true',
|
||||
help='If set, waits until the launched job finishes.'
|
||||
)
|
||||
|
||||
launch_time_options = self.page.connection.options(
|
||||
self.page.endpoint + '1/{}/'.format(self.action)
|
||||
)
|
||||
if with_pk:
|
||||
parser.choices[self.action].add_argument('id', type=functools.partial(pk_or_name, None, self.resource, page=self.page), help='')
|
||||
parser.choices[self.action].add_argument('--monitor', action='store_true', help='If set, prints stdout of the launched job until it finishes.')
|
||||
parser.choices[self.action].add_argument('--timeout', type=int, help='If set with --monitor or --wait, time out waiting on job completion.') # noqa
|
||||
parser.choices[self.action].add_argument('--wait', action='store_true', help='If set, waits until the launched job finishes.')
|
||||
|
||||
launch_time_options = self.page.connection.options(self.page.endpoint + '1/{}/'.format(self.action))
|
||||
if launch_time_options.ok:
|
||||
launch_time_options = launch_time_options.json()['actions']['POST']
|
||||
resource_options_parser.options['LAUNCH'] = launch_time_options
|
||||
@@ -118,24 +100,15 @@ class ProjectCreate(CustomAction):
|
||||
resource = 'projects'
|
||||
|
||||
def add_arguments(self, parser, resource_options_parser):
|
||||
parser.choices[self.action].add_argument(
|
||||
'--monitor', action='store_true',
|
||||
help=('If set, prints stdout of the project update until '
|
||||
'it finishes.')
|
||||
)
|
||||
parser.choices[self.action].add_argument(
|
||||
'--wait', action='store_true',
|
||||
help='If set, waits until the new project has updated.'
|
||||
)
|
||||
parser.choices[self.action].add_argument('--monitor', action='store_true', help=('If set, prints stdout of the project update until ' 'it finishes.'))
|
||||
parser.choices[self.action].add_argument('--wait', action='store_true', help='If set, waits until the new project has updated.')
|
||||
|
||||
def post(self, kwargs):
|
||||
should_monitor = kwargs.pop('monitor', False)
|
||||
wait = kwargs.pop('wait', False)
|
||||
response = self.page.post(kwargs)
|
||||
if should_monitor or wait:
|
||||
update = response.related.project_updates.get(
|
||||
order_by='-created'
|
||||
).results[0]
|
||||
update = response.related.project_updates.get(order_by='-created').results[0]
|
||||
monitor(
|
||||
update,
|
||||
self.page.connection.session,
|
||||
@@ -154,9 +127,7 @@ class AdhocCommandLaunch(Launchable, CustomAction):
|
||||
resource = 'ad_hoc_commands'
|
||||
|
||||
def add_arguments(self, parser, resource_options_parser):
|
||||
Launchable.add_arguments(
|
||||
self, parser, resource_options_parser, with_pk=False
|
||||
)
|
||||
Launchable.add_arguments(self, parser, resource_options_parser, with_pk=False)
|
||||
|
||||
def perform(self, **kwargs):
|
||||
monitor_kwargs = {
|
||||
@@ -182,22 +153,14 @@ class HasStdout(object):
|
||||
|
||||
def add_arguments(self, parser, resource_options_parser):
|
||||
from .options import pk_or_name
|
||||
parser.choices['stdout'].add_argument(
|
||||
'id',
|
||||
type=functools.partial(
|
||||
pk_or_name, None, self.resource, page=self.page
|
||||
),
|
||||
help=''
|
||||
)
|
||||
|
||||
parser.choices['stdout'].add_argument('id', type=functools.partial(pk_or_name, None, self.resource, page=self.page), help='')
|
||||
|
||||
def perform(self):
|
||||
fmt = 'txt_download'
|
||||
if color_enabled():
|
||||
fmt = 'ansi_download'
|
||||
return self.page.connection.get(
|
||||
self.page.get().related.stdout,
|
||||
query_parameters=dict(format=fmt)
|
||||
).content.decode('utf-8')
|
||||
return self.page.connection.get(self.page.get().related.stdout, query_parameters=dict(format=fmt)).content.decode('utf-8')
|
||||
|
||||
|
||||
class JobStdout(HasStdout, CustomAction):
|
||||
@@ -222,13 +185,8 @@ class AssociationMixin(object):
|
||||
|
||||
def add_arguments(self, parser, resource_options_parser):
|
||||
from .options import pk_or_name
|
||||
parser.choices[self.action].add_argument(
|
||||
'id',
|
||||
type=functools.partial(
|
||||
pk_or_name, None, self.resource, page=self.page
|
||||
),
|
||||
help=''
|
||||
)
|
||||
|
||||
parser.choices[self.action].add_argument('id', type=functools.partial(pk_or_name, None, self.resource, page=self.page), help='')
|
||||
group = parser.choices[self.action].add_mutually_exclusive_group(required=True)
|
||||
for param, endpoint in self.targets.items():
|
||||
field, model_name = endpoint
|
||||
@@ -237,7 +195,6 @@ class AssociationMixin(object):
|
||||
help_text = 'The ID (or name) of the {} to {}'.format(model_name, self.action)
|
||||
|
||||
class related_page(object):
|
||||
|
||||
def __init__(self, connection, resource):
|
||||
self.conn = connection
|
||||
self.resource = {
|
||||
@@ -256,20 +213,15 @@ class AssociationMixin(object):
|
||||
group.add_argument(
|
||||
'--{}'.format(param),
|
||||
metavar='',
|
||||
type=functools.partial(
|
||||
pk_or_name, None, param,
|
||||
page=related_page(self.page.connection, param)
|
||||
),
|
||||
help=help_text
|
||||
type=functools.partial(pk_or_name, None, param, page=related_page(self.page.connection, param)),
|
||||
help=help_text,
|
||||
)
|
||||
|
||||
def perform(self, **kwargs):
|
||||
for k, v in kwargs.items():
|
||||
endpoint, _ = self.targets[k]
|
||||
try:
|
||||
self.page.get().related[endpoint].post(
|
||||
{'id': v, self.action: True}
|
||||
)
|
||||
self.page.get().related[endpoint].post({'id': v, self.action: True})
|
||||
except NoContent:
|
||||
# we expect to enter this block because these endpoints return
|
||||
# HTTP 204 on success
|
||||
@@ -279,18 +231,9 @@ class AssociationMixin(object):
|
||||
|
||||
class NotificationAssociateMixin(AssociationMixin):
|
||||
targets = {
|
||||
'start_notification': [
|
||||
'notification_templates_started',
|
||||
'notification_template'
|
||||
],
|
||||
'success_notification': [
|
||||
'notification_templates_success',
|
||||
'notification_template'
|
||||
],
|
||||
'failure_notification': [
|
||||
'notification_templates_error',
|
||||
'notification_template'
|
||||
],
|
||||
'start_notification': ['notification_templates_started', 'notification_template'],
|
||||
'success_notification': ['notification_templates_success', 'notification_template'],
|
||||
'failure_notification': ['notification_templates_error', 'notification_template'],
|
||||
}
|
||||
|
||||
|
||||
@@ -306,12 +249,16 @@ class JobTemplateNotificationDisAssociation(NotificationAssociateMixin, CustomAc
|
||||
targets = NotificationAssociateMixin.targets.copy()
|
||||
|
||||
|
||||
JobTemplateNotificationAssociation.targets.update({
|
||||
'credential': ['credentials', None],
|
||||
})
|
||||
JobTemplateNotificationDisAssociation.targets.update({
|
||||
'credential': ['credentials', None],
|
||||
})
|
||||
JobTemplateNotificationAssociation.targets.update(
|
||||
{
|
||||
'credential': ['credentials', None],
|
||||
}
|
||||
)
|
||||
JobTemplateNotificationDisAssociation.targets.update(
|
||||
{
|
||||
'credential': ['credentials', None],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class WorkflowJobTemplateNotificationAssociation(NotificationAssociateMixin, CustomAction):
|
||||
@@ -326,12 +273,16 @@ class WorkflowJobTemplateNotificationDisAssociation(NotificationAssociateMixin,
|
||||
targets = NotificationAssociateMixin.targets.copy()
|
||||
|
||||
|
||||
WorkflowJobTemplateNotificationAssociation.targets.update({
|
||||
'approval_notification': ['notification_templates_approvals', 'notification_template'],
|
||||
})
|
||||
WorkflowJobTemplateNotificationDisAssociation.targets.update({
|
||||
'approval_notification': ['notification_templates_approvals', 'notification_template'],
|
||||
})
|
||||
WorkflowJobTemplateNotificationAssociation.targets.update(
|
||||
{
|
||||
'approval_notification': ['notification_templates_approvals', 'notification_template'],
|
||||
}
|
||||
)
|
||||
WorkflowJobTemplateNotificationDisAssociation.targets.update(
|
||||
{
|
||||
'approval_notification': ['notification_templates_approvals', 'notification_template'],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class ProjectNotificationAssociation(NotificationAssociateMixin, CustomAction):
|
||||
@@ -366,14 +317,18 @@ class OrganizationNotificationDisAssociation(NotificationAssociateMixin, CustomA
|
||||
targets = NotificationAssociateMixin.targets.copy()
|
||||
|
||||
|
||||
OrganizationNotificationAssociation.targets.update({
|
||||
'approval_notification': ['notification_templates_approvals', 'notification_template'],
|
||||
'galaxy_credential': ['galaxy_credentials', 'credential'],
|
||||
})
|
||||
OrganizationNotificationDisAssociation.targets.update({
|
||||
'approval_notification': ['notification_templates_approvals', 'notification_template'],
|
||||
'galaxy_credential': ['galaxy_credentials', 'credential'],
|
||||
})
|
||||
OrganizationNotificationAssociation.targets.update(
|
||||
{
|
||||
'approval_notification': ['notification_templates_approvals', 'notification_template'],
|
||||
'galaxy_credential': ['galaxy_credentials', 'credential'],
|
||||
}
|
||||
)
|
||||
OrganizationNotificationDisAssociation.targets.update(
|
||||
{
|
||||
'approval_notification': ['notification_templates_approvals', 'notification_template'],
|
||||
'galaxy_credential': ['galaxy_credentials', 'credential'],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class SettingsList(CustomAction):
|
||||
@@ -381,9 +336,7 @@ class SettingsList(CustomAction):
|
||||
resource = 'settings'
|
||||
|
||||
def add_arguments(self, parser, resource_options_parser):
|
||||
parser.choices['list'].add_argument(
|
||||
'--slug', help='optional setting category/slug', default='all'
|
||||
)
|
||||
parser.choices['list'].add_argument('--slug', help='optional setting category/slug', default='all')
|
||||
|
||||
def perform(self, slug):
|
||||
self.page.endpoint = self.page.endpoint + '{}/'.format(slug)
|
||||
@@ -409,30 +362,18 @@ class RoleMixin(object):
|
||||
|
||||
if not RoleMixin.roles:
|
||||
for resource, flag in self.has_roles:
|
||||
options = self.page.__class__(
|
||||
self.page.endpoint.replace(self.resource, resource),
|
||||
self.page.connection
|
||||
).options()
|
||||
RoleMixin.roles[flag] = [
|
||||
role.replace('_role', '')
|
||||
for role in options.json.get('object_roles', [])
|
||||
]
|
||||
options = self.page.__class__(self.page.endpoint.replace(self.resource, resource), self.page.connection).options()
|
||||
RoleMixin.roles[flag] = [role.replace('_role', '') for role in options.json.get('object_roles', [])]
|
||||
|
||||
possible_roles = set()
|
||||
for v in RoleMixin.roles.values():
|
||||
possible_roles.update(v)
|
||||
|
||||
resource_group = parser.choices[self.action].add_mutually_exclusive_group(
|
||||
required=True
|
||||
)
|
||||
resource_group = parser.choices[self.action].add_mutually_exclusive_group(required=True)
|
||||
parser.choices[self.action].add_argument(
|
||||
'id',
|
||||
type=functools.partial(
|
||||
pk_or_name, None, self.resource, page=self.page
|
||||
),
|
||||
help='The ID (or name) of the {} to {} access to/from'.format(
|
||||
self.resource, self.action
|
||||
)
|
||||
type=functools.partial(pk_or_name, None, self.resource, page=self.page),
|
||||
help='The ID (or name) of the {} to {} access to/from'.format(self.resource, self.action),
|
||||
)
|
||||
for _type in RoleMixin.roles.keys():
|
||||
if _type == 'team' and self.resource == 'team':
|
||||
@@ -440,7 +381,6 @@ class RoleMixin(object):
|
||||
continue
|
||||
|
||||
class related_page(object):
|
||||
|
||||
def __init__(self, connection, resource):
|
||||
self.conn = connection
|
||||
if resource == 'inventories':
|
||||
@@ -453,19 +393,12 @@ class RoleMixin(object):
|
||||
|
||||
resource_group.add_argument(
|
||||
'--{}'.format(_type),
|
||||
type=functools.partial(
|
||||
pk_or_name, None, _type,
|
||||
page=related_page(
|
||||
self.page.connection,
|
||||
dict((v, k) for k, v in self.has_roles)[_type]
|
||||
)
|
||||
),
|
||||
type=functools.partial(pk_or_name, None, _type, page=related_page(self.page.connection, dict((v, k) for k, v in self.has_roles)[_type])),
|
||||
metavar='ID',
|
||||
help='The ID (or name) of the target {}'.format(_type),
|
||||
)
|
||||
parser.choices[self.action].add_argument(
|
||||
'--role', type=str, choices=possible_roles, required=True,
|
||||
help='The name of the role to {}'.format(self.action)
|
||||
'--role', type=str, choices=possible_roles, required=True, help='The name of the role to {}'.format(self.action)
|
||||
)
|
||||
|
||||
def perform(self, **kwargs):
|
||||
@@ -474,17 +407,10 @@ class RoleMixin(object):
|
||||
role = kwargs['role']
|
||||
if role not in RoleMixin.roles[flag]:
|
||||
options = ', '.join(RoleMixin.roles[flag])
|
||||
raise ValueError(
|
||||
"invalid choice: '{}' must be one of {}".format(
|
||||
role, options
|
||||
)
|
||||
)
|
||||
raise ValueError("invalid choice: '{}' must be one of {}".format(role, options))
|
||||
value = kwargs[flag]
|
||||
target = '/api/v2/{}/{}'.format(resource, value)
|
||||
detail = self.page.__class__(
|
||||
target,
|
||||
self.page.connection
|
||||
).get()
|
||||
detail = self.page.__class__(target, self.page.connection).get()
|
||||
object_roles = detail['summary_fields']['object_roles']
|
||||
actual_role = object_roles[role + '_role']
|
||||
params = {'id': actual_role['id']}
|
||||
@@ -530,15 +456,8 @@ class SettingsModify(CustomAction):
|
||||
resource = 'settings'
|
||||
|
||||
def add_arguments(self, parser, resource_options_parser):
|
||||
options = self.page.__class__(
|
||||
self.page.endpoint + 'all/', self.page.connection
|
||||
).options()
|
||||
parser.choices['modify'].add_argument(
|
||||
'key',
|
||||
choices=sorted(options['actions']['PUT'].keys()),
|
||||
metavar='key',
|
||||
help=''
|
||||
)
|
||||
options = self.page.__class__(self.page.endpoint + 'all/', self.page.connection).options()
|
||||
parser.choices['modify'].add_argument('key', choices=sorted(options['actions']['PUT'].keys()), metavar='key', help='')
|
||||
parser.choices['modify'].add_argument('value', help='')
|
||||
|
||||
def perform(self, key, value):
|
||||
@@ -563,13 +482,8 @@ class HasMonitor(object):
|
||||
|
||||
def add_arguments(self, parser, resource_options_parser):
|
||||
from .options import pk_or_name
|
||||
parser.choices[self.action].add_argument(
|
||||
'id',
|
||||
type=functools.partial(
|
||||
pk_or_name, None, self.resource, page=self.page
|
||||
),
|
||||
help=''
|
||||
)
|
||||
|
||||
parser.choices[self.action].add_argument('id', type=functools.partial(pk_or_name, None, self.resource, page=self.page), help='')
|
||||
|
||||
def perform(self, **kwargs):
|
||||
response = self.page.get()
|
||||
|
||||
@@ -27,9 +27,7 @@ author = 'Ansible by Red Hat'
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'awxkit.cli.sphinx'
|
||||
]
|
||||
extensions = ['awxkit.cli.sphinx']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
@@ -48,24 +48,21 @@ def add_output_formatting_arguments(parser, env):
|
||||
dest='conf.format',
|
||||
choices=FORMATTERS.keys(),
|
||||
default=env.get('TOWER_FORMAT', 'json'),
|
||||
help=(
|
||||
'specify a format for the input and output'
|
||||
),
|
||||
help=('specify a format for the input and output'),
|
||||
)
|
||||
formatting.add_argument(
|
||||
'--filter',
|
||||
dest='conf.filter',
|
||||
default='.',
|
||||
metavar='TEXT',
|
||||
help=(
|
||||
'specify an output filter (only valid with jq or human format)'
|
||||
),
|
||||
help=('specify an output filter (only valid with jq or human format)'),
|
||||
)
|
||||
formatting.add_argument(
|
||||
'--conf.color',
|
||||
metavar='BOOLEAN',
|
||||
help='Display colorized output. Defaults to True',
|
||||
default=env.get('TOWER_COLOR', 't'), type=strtobool,
|
||||
default=env.get('TOWER_COLOR', 't'),
|
||||
type=strtobool,
|
||||
)
|
||||
formatting.add_argument(
|
||||
'-v',
|
||||
@@ -73,7 +70,7 @@ def add_output_formatting_arguments(parser, env):
|
||||
dest='conf.verbose',
|
||||
help='print debug-level logs, including requests made',
|
||||
default=strtobool(env.get('TOWER_VERBOSE', 'f')),
|
||||
action="store_true"
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
|
||||
@@ -105,11 +102,10 @@ def format_jq(output, fmt):
|
||||
if fmt == '.':
|
||||
return output
|
||||
raise ImportError(
|
||||
'To use `-f jq`, you must install the optional jq dependency.\n'
|
||||
'`pip install jq`\n',
|
||||
'To use `-f jq`, you must install the optional jq dependency.\n' '`pip install jq`\n',
|
||||
'Note that some platforms may require additional programs to '
|
||||
'build jq from source (like `libtool`).\n'
|
||||
'See https://pypi.org/project/jq/ for instructions.'
|
||||
'See https://pypi.org/project/jq/ for instructions.',
|
||||
)
|
||||
results = []
|
||||
for x in jq.jq(fmt).transform(output, multiple_output=True):
|
||||
@@ -127,11 +123,7 @@ def format_json(output, fmt):
|
||||
|
||||
def format_yaml(output, fmt):
|
||||
output = json.loads(json.dumps(output))
|
||||
return yaml.safe_dump(
|
||||
output,
|
||||
default_flow_style=False,
|
||||
allow_unicode=True
|
||||
)
|
||||
return yaml.safe_dump(output, default_flow_style=False, allow_unicode=True)
|
||||
|
||||
|
||||
def format_human(output, fmt):
|
||||
@@ -151,10 +143,7 @@ def format_human(output, fmt):
|
||||
column_names.remove(k)
|
||||
|
||||
table = [column_names]
|
||||
table.extend([
|
||||
[record.get(col, '') for col in column_names]
|
||||
for record in output
|
||||
])
|
||||
table.extend([[record.get(col, '') for col in column_names] for record in output])
|
||||
col_paddings = []
|
||||
|
||||
def format_num(v):
|
||||
@@ -184,9 +173,4 @@ def format_human(output, fmt):
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
FORMATTERS = {
|
||||
'json': format_json,
|
||||
'yaml': format_yaml,
|
||||
'jq': format_jq,
|
||||
'human': format_human
|
||||
}
|
||||
FORMATTERS = {'json': format_json, 'yaml': format_yaml, 'jq': format_jq, 'human': format_human}
|
||||
|
||||
@@ -21,10 +21,7 @@ UNIQUENESS_RULES = {
|
||||
|
||||
|
||||
def pk_or_name_list(v2, model_name, value, page=None):
|
||||
return [
|
||||
pk_or_name(v2, model_name, v.strip(), page=page)
|
||||
for v in value.split(',')
|
||||
]
|
||||
return [pk_or_name(v2, model_name, v.strip(), page=page) for v in value.split(',')]
|
||||
|
||||
|
||||
def pk_or_name(v2, model_name, value, page=None):
|
||||
@@ -58,17 +55,9 @@ def pk_or_name(v2, model_name, value, page=None):
|
||||
return int(results.results[0].id)
|
||||
if results.count > 1:
|
||||
raise argparse.ArgumentTypeError(
|
||||
'Multiple {0} exist with that {1}. '
|
||||
'To look up an ID, run:\n'
|
||||
'awx {0} list --{1} "{2}" -f human'.format(
|
||||
model_name, identity, value
|
||||
)
|
||||
'Multiple {0} exist with that {1}. ' 'To look up an ID, run:\n' 'awx {0} list --{1} "{2}" -f human'.format(model_name, identity, value)
|
||||
)
|
||||
raise argparse.ArgumentTypeError(
|
||||
'Could not find any {0} with that {1}.'.format(
|
||||
model_name, identity
|
||||
)
|
||||
)
|
||||
raise argparse.ArgumentTypeError('Could not find any {0} with that {1}.'.format(model_name, identity))
|
||||
|
||||
return value
|
||||
|
||||
@@ -90,9 +79,7 @@ class ResourceOptionsParser(object):
|
||||
self.page = page
|
||||
self.resource = resource
|
||||
self.parser = parser
|
||||
self.options = getattr(
|
||||
self.page.options().json, 'actions', {'GET': {}}
|
||||
)
|
||||
self.options = getattr(self.page.options().json, 'actions', {'GET': {}})
|
||||
self.get_allowed_options()
|
||||
if self.resource != 'settings':
|
||||
# /api/v2/settings is a special resource that doesn't have
|
||||
@@ -103,9 +90,7 @@ class ResourceOptionsParser(object):
|
||||
self.handle_custom_actions()
|
||||
|
||||
def get_allowed_options(self):
|
||||
options = self.page.connection.options(
|
||||
self.page.endpoint + '1/'
|
||||
)
|
||||
options = self.page.connection.options(self.page.endpoint + '1/')
|
||||
warning = options.headers.get('Warning', '')
|
||||
if '299' in warning and 'deprecated' in warning:
|
||||
self.deprecated = True
|
||||
@@ -121,11 +106,10 @@ class ResourceOptionsParser(object):
|
||||
parser = self.parser.add_parser(method, help='')
|
||||
if method == 'list':
|
||||
parser.add_argument(
|
||||
'--all', dest='all_pages', action='store_true',
|
||||
help=(
|
||||
'fetch all pages of content from the API when '
|
||||
'returning results (instead of just the first page)'
|
||||
)
|
||||
'--all',
|
||||
dest='all_pages',
|
||||
action='store_true',
|
||||
help=('fetch all pages of content from the API when ' 'returning results (instead of just the first page)'),
|
||||
)
|
||||
add_output_formatting_arguments(parser, {})
|
||||
|
||||
@@ -138,9 +122,7 @@ class ResourceOptionsParser(object):
|
||||
for method in allowed:
|
||||
parser = self.parser.add_parser(method, help='')
|
||||
self.parser.choices[method].add_argument(
|
||||
'id',
|
||||
type=functools.partial(pk_or_name, self.v2, self.resource),
|
||||
help='the ID (or unique name) of the resource'
|
||||
'id', type=functools.partial(pk_or_name, self.v2, self.resource), help='the ID (or unique name) of the resource'
|
||||
)
|
||||
if method == 'get':
|
||||
add_output_formatting_arguments(parser, {})
|
||||
@@ -148,10 +130,7 @@ class ResourceOptionsParser(object):
|
||||
def build_query_arguments(self, method, http_method):
|
||||
required_group = None
|
||||
for k, param in self.options.get(http_method, {}).items():
|
||||
required = (
|
||||
method == 'create' and
|
||||
param.get('required', False) is True
|
||||
)
|
||||
required = method == 'create' and param.get('required', False) is True
|
||||
help_text = param.get('help_text', '')
|
||||
|
||||
if method == 'list':
|
||||
@@ -159,10 +138,7 @@ class ResourceOptionsParser(object):
|
||||
# don't allow `awx <resource> list` to filter on `--id`
|
||||
# it's weird, and that's what awx <resource> get is for
|
||||
continue
|
||||
help_text = 'only list {} with the specified {}'.format(
|
||||
self.resource,
|
||||
k
|
||||
)
|
||||
help_text = 'only list {} with the specified {}'.format(self.resource, k)
|
||||
|
||||
if method == 'list' and param.get('filterable') is False:
|
||||
continue
|
||||
@@ -256,9 +232,8 @@ class ResourceOptionsParser(object):
|
||||
# unlike *other* actual JSON fields in the API, inventory and JT
|
||||
# variables *actually* want json.dumps() strings (ugh)
|
||||
# see: https://github.com/ansible/awx/issues/2371
|
||||
if (
|
||||
(self.resource in ('job_templates', 'workflow_job_templates') and k == 'extra_vars') or
|
||||
(self.resource in ('inventory', 'groups', 'hosts') and k == 'variables')
|
||||
if (self.resource in ('job_templates', 'workflow_job_templates') and k == 'extra_vars') or (
|
||||
self.resource in ('inventory', 'groups', 'hosts') and k == 'variables'
|
||||
):
|
||||
kwargs['type'] = jsonstr
|
||||
|
||||
@@ -267,15 +242,9 @@ class ResourceOptionsParser(object):
|
||||
required_group = self.parser.choices[method].add_argument_group('required arguments')
|
||||
# put the required group first (before the optional args group)
|
||||
self.parser.choices[method]._action_groups.reverse()
|
||||
required_group.add_argument(
|
||||
'--{}'.format(k),
|
||||
**kwargs
|
||||
)
|
||||
required_group.add_argument('--{}'.format(k), **kwargs)
|
||||
else:
|
||||
self.parser.choices[method].add_argument(
|
||||
'--{}'.format(k),
|
||||
**kwargs
|
||||
)
|
||||
self.parser.choices[method].add_argument('--{}'.format(k), **kwargs)
|
||||
|
||||
def handle_custom_actions(self):
|
||||
for _, action in CustomAction.registry.items():
|
||||
|
||||
@@ -40,11 +40,9 @@ DEPRECATED_RESOURCES = {
|
||||
'teams': 'team',
|
||||
'workflow_job_templates': 'workflow',
|
||||
'workflow_jobs': 'workflow_job',
|
||||
'users': 'user'
|
||||
'users': 'user',
|
||||
}
|
||||
DEPRECATED_RESOURCES_REVERSE = dict(
|
||||
(v, k) for k, v in DEPRECATED_RESOURCES.items()
|
||||
)
|
||||
DEPRECATED_RESOURCES_REVERSE = dict((v, k) for k, v in DEPRECATED_RESOURCES.items())
|
||||
|
||||
|
||||
class CustomCommand(metaclass=CustomRegistryMeta):
|
||||
@@ -81,9 +79,7 @@ class Login(CustomCommand):
|
||||
auth.add_argument('--description', help='description of the generated OAuth2.0 token', metavar='TEXT')
|
||||
auth.add_argument('--conf.client_id', metavar='TEXT')
|
||||
auth.add_argument('--conf.client_secret', metavar='TEXT')
|
||||
auth.add_argument(
|
||||
'--conf.scope', choices=['read', 'write'], default='write'
|
||||
)
|
||||
auth.add_argument('--conf.scope', choices=['read', 'write'], default='write')
|
||||
if client.help:
|
||||
self.print_help(parser)
|
||||
raise SystemExit()
|
||||
@@ -99,10 +95,7 @@ class Login(CustomCommand):
|
||||
token = api.Api().get_oauth2_token(**kwargs)
|
||||
except Exception as e:
|
||||
self.print_help(parser)
|
||||
cprint(
|
||||
'Error retrieving an OAuth2.0 token ({}).'.format(e.__class__),
|
||||
'red'
|
||||
)
|
||||
cprint('Error retrieving an OAuth2.0 token ({}).'.format(e.__class__), 'red')
|
||||
else:
|
||||
fmt = client.get_config('format')
|
||||
if fmt == 'human':
|
||||
@@ -186,9 +179,7 @@ def parse_resource(client, skip_deprecated=False):
|
||||
|
||||
# check if the user is running a custom command
|
||||
for command in CustomCommand.__subclasses__():
|
||||
client.subparsers[command.name] = subparsers.add_parser(
|
||||
command.name, help=command.help_text
|
||||
)
|
||||
client.subparsers[command.name] = subparsers.add_parser(command.name, help=command.help_text)
|
||||
|
||||
if hasattr(client, 'v2'):
|
||||
for k in client.v2.json.keys():
|
||||
@@ -202,15 +193,11 @@ def parse_resource(client, skip_deprecated=False):
|
||||
if k in DEPRECATED_RESOURCES:
|
||||
kwargs['aliases'] = [DEPRECATED_RESOURCES[k]]
|
||||
|
||||
client.subparsers[k] = subparsers.add_parser(
|
||||
k, help='', **kwargs
|
||||
)
|
||||
client.subparsers[k] = subparsers.add_parser(k, help='', **kwargs)
|
||||
|
||||
resource = client.parser.parse_known_args()[0].resource
|
||||
if resource in DEPRECATED_RESOURCES.values():
|
||||
client.argv[
|
||||
client.argv.index(resource)
|
||||
] = DEPRECATED_RESOURCES_REVERSE[resource]
|
||||
client.argv[client.argv.index(resource)] = DEPRECATED_RESOURCES_REVERSE[resource]
|
||||
resource = DEPRECATED_RESOURCES_REVERSE[resource]
|
||||
|
||||
if resource in CustomCommand.registry:
|
||||
@@ -219,27 +206,14 @@ def parse_resource(client, skip_deprecated=False):
|
||||
response = command.handle(client, parser)
|
||||
if response:
|
||||
_filter = client.get_config('filter')
|
||||
if (
|
||||
resource == 'config' and
|
||||
client.get_config('format') == 'human'
|
||||
):
|
||||
response = {
|
||||
'count': len(response),
|
||||
'results': [
|
||||
{'key': k, 'value': v}
|
||||
for k, v in response.items()
|
||||
]
|
||||
}
|
||||
if resource == 'config' and client.get_config('format') == 'human':
|
||||
response = {'count': len(response), 'results': [{'key': k, 'value': v} for k, v in response.items()]}
|
||||
_filter = 'key, value'
|
||||
try:
|
||||
connection = client.root.connection
|
||||
except AttributeError:
|
||||
connection = None
|
||||
formatted = format_response(
|
||||
Page.from_json(response, connection=connection),
|
||||
fmt=client.get_config('format'),
|
||||
filter=_filter
|
||||
)
|
||||
formatted = format_response(Page.from_json(response, connection=connection), fmt=client.get_config('format'), filter=_filter)
|
||||
print(formatted)
|
||||
raise SystemExit()
|
||||
else:
|
||||
|
||||
@@ -8,7 +8,6 @@ from .resource import is_control_resource, CustomCommand
|
||||
|
||||
|
||||
class CustomAutoprogramDirective(AutoprogramDirective):
|
||||
|
||||
def run(self):
|
||||
nodes = super(CustomAutoprogramDirective, self).run()
|
||||
|
||||
@@ -23,12 +22,7 @@ class CustomAutoprogramDirective(AutoprogramDirective):
|
||||
nodes[0][0].children = [heading]
|
||||
|
||||
# add a descriptive top synopsis of the reference guide
|
||||
nodes[0].children.insert(1, paragraph(
|
||||
text=(
|
||||
'This is an exhaustive guide of every available command in '
|
||||
'the awx CLI tool.'
|
||||
)
|
||||
))
|
||||
nodes[0].children.insert(1, paragraph(text=('This is an exhaustive guide of every available command in ' 'the awx CLI tool.')))
|
||||
disclaimer = (
|
||||
'The commands and parameters documented here can (and will) '
|
||||
'vary based on a variety of factors, such as the AWX API '
|
||||
@@ -51,9 +45,7 @@ def render():
|
||||
# Sphinx document from.
|
||||
for e in ('TOWER_HOST', 'TOWER_USERNAME', 'TOWER_PASSWORD'):
|
||||
if not os.environ.get(e):
|
||||
raise SystemExit(
|
||||
'Please specify a valid {} for a real (running) Tower install.'.format(e) # noqa
|
||||
)
|
||||
raise SystemExit('Please specify a valid {} for a real (running) Tower install.'.format(e)) # noqa
|
||||
cli = CLI()
|
||||
cli.parse_args(['awx', '--help'])
|
||||
cli.connect()
|
||||
|
||||
@@ -9,8 +9,7 @@ from .utils import cprint, color_enabled, STATUS_COLORS
|
||||
from awxkit.utils import to_str
|
||||
|
||||
|
||||
def monitor_workflow(response, session, print_stdout=True, timeout=None,
|
||||
interval=.25):
|
||||
def monitor_workflow(response, session, print_stdout=True, timeout=None, interval=0.25):
|
||||
get = response.url.get
|
||||
payload = {
|
||||
'order_by': 'finished',
|
||||
@@ -18,9 +17,7 @@ def monitor_workflow(response, session, print_stdout=True, timeout=None,
|
||||
}
|
||||
|
||||
def fetch(seen):
|
||||
results = response.connection.get(
|
||||
'/api/v2/unified_jobs', payload
|
||||
).json()['results']
|
||||
results = response.connection.get('/api/v2/unified_jobs', payload).json()['results']
|
||||
|
||||
# erase lines we've previously printed
|
||||
if print_stdout and sys.stdout.isatty():
|
||||
@@ -61,7 +58,7 @@ def monitor_workflow(response, session, print_stdout=True, timeout=None,
|
||||
# all at the end
|
||||
fetch(seen)
|
||||
|
||||
time.sleep(.25)
|
||||
time.sleep(0.25)
|
||||
json = get().json
|
||||
if json.finished:
|
||||
fetch(seen)
|
||||
@@ -71,7 +68,7 @@ def monitor_workflow(response, session, print_stdout=True, timeout=None,
|
||||
return get().json.status
|
||||
|
||||
|
||||
def monitor(response, session, print_stdout=True, timeout=None, interval=.25):
|
||||
def monitor(response, session, print_stdout=True, timeout=None, interval=0.25):
|
||||
get = response.url.get
|
||||
payload = {'order_by': 'start_line', 'no_truncate': True}
|
||||
if response.type == 'job':
|
||||
@@ -108,12 +105,9 @@ def monitor(response, session, print_stdout=True, timeout=None, interval=.25):
|
||||
if next_line:
|
||||
payload['start_line__gte'] = next_line
|
||||
|
||||
time.sleep(.25)
|
||||
time.sleep(0.25)
|
||||
json = get().json
|
||||
if (
|
||||
json.event_processing_finished is True or
|
||||
json.status in ('error', 'canceled')
|
||||
):
|
||||
if json.event_processing_finished is True or json.status in ('error', 'canceled'):
|
||||
fetch(next_line)
|
||||
break
|
||||
if print_stdout:
|
||||
|
||||
@@ -9,8 +9,7 @@ _color = threading.local()
|
||||
_color.enabled = True
|
||||
|
||||
|
||||
__all__ = ['CustomRegistryMeta', 'HelpfulArgumentParser', 'disable_color',
|
||||
'color_enabled', 'colored', 'cprint', 'STATUS_COLORS']
|
||||
__all__ = ['CustomRegistryMeta', 'HelpfulArgumentParser', 'disable_color', 'color_enabled', 'colored', 'cprint', 'STATUS_COLORS']
|
||||
|
||||
|
||||
STATUS_COLORS = {
|
||||
@@ -25,17 +24,12 @@ STATUS_COLORS = {
|
||||
|
||||
|
||||
class CustomRegistryMeta(type):
|
||||
|
||||
@property
|
||||
def registry(cls):
|
||||
return dict(
|
||||
(command.name, command)
|
||||
for command in cls.__subclasses__()
|
||||
)
|
||||
return dict((command.name, command) for command in cls.__subclasses__())
|
||||
|
||||
|
||||
class HelpfulArgumentParser(ArgumentParser):
|
||||
|
||||
def error(self, message): # pragma: nocover
|
||||
"""Prints a usage message incorporating the message to stderr and
|
||||
exits.
|
||||
@@ -67,10 +61,16 @@ COLORS = dict(
|
||||
list(
|
||||
zip(
|
||||
[
|
||||
'grey', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan',
|
||||
'grey',
|
||||
'red',
|
||||
'green',
|
||||
'yellow',
|
||||
'blue',
|
||||
'magenta',
|
||||
'cyan',
|
||||
'white',
|
||||
],
|
||||
list(range(30, 38))
|
||||
list(range(30, 38)),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user