mirror of
https://github.com/ansible/awx.git
synced 2026-03-06 11:11:07 -03:30
properly parse CLI arguments for launch endpoints
see: https://github.com/ansible/awx/issues/5093
This commit is contained in:
@@ -158,9 +158,16 @@ class Metadata(metadata.SimpleMetadata):
|
|||||||
isinstance(field, JSONField) or
|
isinstance(field, JSONField) or
|
||||||
isinstance(model_field, JSONField) or
|
isinstance(model_field, JSONField) or
|
||||||
isinstance(field, DRFJSONField) or
|
isinstance(field, DRFJSONField) or
|
||||||
isinstance(getattr(field, 'model_field', None), JSONField)
|
isinstance(getattr(field, 'model_field', None), JSONField) or
|
||||||
|
field.field_name == 'credential_passwords'
|
||||||
):
|
):
|
||||||
field_info['type'] = 'json'
|
field_info['type'] = 'json'
|
||||||
|
elif (
|
||||||
|
isinstance(field, ManyRelatedField) and
|
||||||
|
field.field_name == 'credentials'
|
||||||
|
# launch-time credentials
|
||||||
|
):
|
||||||
|
field_info['type'] = 'list_of_ids'
|
||||||
elif isinstance(model_field, BooleanField):
|
elif isinstance(model_field, BooleanField):
|
||||||
field_info['type'] = 'boolean'
|
field_info['type'] = 'boolean'
|
||||||
|
|
||||||
|
|||||||
@@ -41,13 +41,13 @@ class CustomAction(with_metaclass(CustomActionRegistryMeta)):
|
|||||||
def perform(self):
|
def perform(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser, resource_options_parser):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Launchable(object):
|
class Launchable(object):
|
||||||
|
|
||||||
def add_arguments(self, parser, with_pk=True):
|
def add_arguments(self, parser, resource_options_parser, with_pk=True):
|
||||||
from .options import pk_or_name
|
from .options import pk_or_name
|
||||||
if with_pk:
|
if with_pk:
|
||||||
parser.choices[self.action].add_argument(
|
parser.choices[self.action].add_argument(
|
||||||
@@ -70,6 +70,14 @@ class Launchable(object):
|
|||||||
help='If set, waits until the launched job finishes.'
|
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
|
||||||
|
resource_options_parser.build_query_arguments(self.action, 'LAUNCH')
|
||||||
|
|
||||||
def monitor(self, response, **kwargs):
|
def monitor(self, response, **kwargs):
|
||||||
mon = monitor_workflow if response.type == 'workflow_job' else monitor
|
mon = monitor_workflow if response.type == 'workflow_job' else monitor
|
||||||
if kwargs.get('monitor') or kwargs.get('wait'):
|
if kwargs.get('monitor') or kwargs.get('wait'):
|
||||||
@@ -84,8 +92,13 @@ class Launchable(object):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
def perform(self, **kwargs):
|
def perform(self, **kwargs):
|
||||||
response = self.page.get().related.get(self.action).post()
|
monitor_kwargs = {
|
||||||
self.monitor(response, **kwargs)
|
'monitor': kwargs.pop('monitor', False),
|
||||||
|
'wait': kwargs.pop('wait', False),
|
||||||
|
'timeout': kwargs.pop('timeout', False),
|
||||||
|
}
|
||||||
|
response = self.page.get().related.get(self.action).post(kwargs)
|
||||||
|
self.monitor(response, **monitor_kwargs)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@@ -103,7 +116,7 @@ class ProjectCreate(CustomAction):
|
|||||||
action = 'create'
|
action = 'create'
|
||||||
resource = 'projects'
|
resource = 'projects'
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser, resource_options_parser):
|
||||||
parser.choices[self.action].add_argument(
|
parser.choices[self.action].add_argument(
|
||||||
'--monitor', action='store_true',
|
'--monitor', action='store_true',
|
||||||
help=('If set, prints stdout of the project update until '
|
help=('If set, prints stdout of the project update until '
|
||||||
@@ -139,8 +152,10 @@ class AdhocCommandLaunch(Launchable, CustomAction):
|
|||||||
action = 'create'
|
action = 'create'
|
||||||
resource = 'ad_hoc_commands'
|
resource = 'ad_hoc_commands'
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser, resource_options_parser):
|
||||||
Launchable.add_arguments(self, parser, with_pk=False)
|
Launchable.add_arguments(
|
||||||
|
self, parser, resource_options_parser, with_pk=False
|
||||||
|
)
|
||||||
|
|
||||||
def perform(self, **kwargs):
|
def perform(self, **kwargs):
|
||||||
monitor_kwargs = {
|
monitor_kwargs = {
|
||||||
@@ -164,7 +179,7 @@ class HasStdout(object):
|
|||||||
|
|
||||||
action = 'stdout'
|
action = 'stdout'
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser, resource_options_parser):
|
||||||
from .options import pk_or_name
|
from .options import pk_or_name
|
||||||
parser.choices['stdout'].add_argument(
|
parser.choices['stdout'].add_argument(
|
||||||
'id',
|
'id',
|
||||||
@@ -204,7 +219,7 @@ class AssociationMixin(object):
|
|||||||
|
|
||||||
action = 'associate'
|
action = 'associate'
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser, resource_options_parser):
|
||||||
from .options import pk_or_name
|
from .options import pk_or_name
|
||||||
parser.choices[self.action].add_argument(
|
parser.choices[self.action].add_argument(
|
||||||
'id',
|
'id',
|
||||||
@@ -361,7 +376,7 @@ class SettingsList(CustomAction):
|
|||||||
action = 'list'
|
action = 'list'
|
||||||
resource = 'settings'
|
resource = 'settings'
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser, resource_options_parser):
|
||||||
parser.choices['list'].add_argument(
|
parser.choices['list'].add_argument(
|
||||||
'--slug', help='optional setting category/slug', default='all'
|
'--slug', help='optional setting category/slug', default='all'
|
||||||
)
|
)
|
||||||
@@ -385,7 +400,7 @@ class RoleMixin(object):
|
|||||||
]
|
]
|
||||||
roles = {} # this is calculated once
|
roles = {} # this is calculated once
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser, resource_options_parser):
|
||||||
from .options import pk_or_name
|
from .options import pk_or_name
|
||||||
|
|
||||||
if not RoleMixin.roles:
|
if not RoleMixin.roles:
|
||||||
@@ -510,7 +525,7 @@ class SettingsModify(CustomAction):
|
|||||||
action = 'modify'
|
action = 'modify'
|
||||||
resource = 'settings'
|
resource = 'settings'
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser, resource_options_parser):
|
||||||
options = self.page.__class__(
|
options = self.page.__class__(
|
||||||
self.page.endpoint + 'all/', self.page.connection
|
self.page.endpoint + 'all/', self.page.connection
|
||||||
).options()
|
).options()
|
||||||
|
|||||||
@@ -20,6 +20,13 @@ 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(',')
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def pk_or_name(v2, model_name, value, page=None):
|
def pk_or_name(v2, model_name, value, page=None):
|
||||||
if isinstance(value, int):
|
if isinstance(value, int):
|
||||||
return value
|
return value
|
||||||
@@ -193,6 +200,7 @@ class ResourceOptionsParser(object):
|
|||||||
'boolean': strtobool,
|
'boolean': strtobool,
|
||||||
'id': functools.partial(pk_or_name, self.v2, k),
|
'id': functools.partial(pk_or_name, self.v2, k),
|
||||||
'json': json_or_yaml,
|
'json': json_or_yaml,
|
||||||
|
'list_of_ids': functools.partial(pk_or_name_list, self.v2, k),
|
||||||
}.get(param['type'], str),
|
}.get(param['type'], str),
|
||||||
}
|
}
|
||||||
meta_map = {
|
meta_map = {
|
||||||
@@ -200,6 +208,7 @@ class ResourceOptionsParser(object):
|
|||||||
'integer': 'INTEGER',
|
'integer': 'INTEGER',
|
||||||
'boolean': 'BOOLEAN',
|
'boolean': 'BOOLEAN',
|
||||||
'id': 'ID', # foreign key
|
'id': 'ID', # foreign key
|
||||||
|
'list_of_ids': '[ID, ID, ...]',
|
||||||
'json': 'JSON/YAML',
|
'json': 'JSON/YAML',
|
||||||
}
|
}
|
||||||
if param.get('choices', []):
|
if param.get('choices', []):
|
||||||
@@ -209,13 +218,17 @@ class ResourceOptionsParser(object):
|
|||||||
# explicitly tell us in OPTIONS all the time)
|
# explicitly tell us in OPTIONS all the time)
|
||||||
if isinstance(kwargs['choices'][0], int):
|
if isinstance(kwargs['choices'][0], int):
|
||||||
kwargs['type'] = int
|
kwargs['type'] = int
|
||||||
kwargs['choices'] = [str(choice) for choice in kwargs['choices']]
|
else:
|
||||||
|
kwargs['choices'] = [str(choice) for choice in kwargs['choices']]
|
||||||
elif param['type'] in meta_map:
|
elif param['type'] in meta_map:
|
||||||
kwargs['metavar'] = meta_map[param['type']]
|
kwargs['metavar'] = meta_map[param['type']]
|
||||||
|
|
||||||
if param['type'] == 'id' and not kwargs.get('help'):
|
if param['type'] == 'id' and not kwargs.get('help'):
|
||||||
kwargs['help'] = 'the ID of the associated {}'.format(k)
|
kwargs['help'] = 'the ID of the associated {}'.format(k)
|
||||||
|
|
||||||
|
if param['type'] == 'list_of_ids':
|
||||||
|
kwargs['help'] = 'a list of comma-delimited {} to associate (IDs or unique names)'.format(k)
|
||||||
|
|
||||||
if param['type'] == 'json' and method != 'list':
|
if param['type'] == 'json' and method != 'list':
|
||||||
help_parts = []
|
help_parts = []
|
||||||
if kwargs.get('help'):
|
if kwargs.get('help'):
|
||||||
@@ -266,4 +279,4 @@ class ResourceOptionsParser(object):
|
|||||||
continue
|
continue
|
||||||
if action.action not in self.parser.choices:
|
if action.action not in self.parser.choices:
|
||||||
self.parser.add_parser(action.action, help='')
|
self.parser.add_parser(action.action, help='')
|
||||||
action(self.page).add_arguments(self.parser)
|
action(self.page).add_arguments(self.parser, self)
|
||||||
|
|||||||
@@ -128,17 +128,17 @@ class TestOptions(unittest.TestCase):
|
|||||||
page = OptionsPage.from_json({
|
page = OptionsPage.from_json({
|
||||||
'actions': {
|
'actions': {
|
||||||
'POST': {
|
'POST': {
|
||||||
'limit': {'type': 'integer'}
|
'max_hosts': {'type': 'integer'}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
options = ResourceOptionsParser(None, page, 'job_templates', self.parser)
|
options = ResourceOptionsParser(None, page, 'organizations', self.parser)
|
||||||
options.build_query_arguments('create', 'POST')
|
options.build_query_arguments('create', 'POST')
|
||||||
assert 'create' in self.parser.choices
|
assert 'create' in self.parser.choices
|
||||||
|
|
||||||
out = StringIO()
|
out = StringIO()
|
||||||
self.parser.choices['create'].print_help(out)
|
self.parser.choices['create'].print_help(out)
|
||||||
assert '--limit INTEGER' in out.getvalue()
|
assert '--max_hosts INTEGER' in out.getvalue()
|
||||||
|
|
||||||
def test_boolean_argument(self):
|
def test_boolean_argument(self):
|
||||||
page = OptionsPage.from_json({
|
page = OptionsPage.from_json({
|
||||||
|
|||||||
Reference in New Issue
Block a user