Allow use of credential password prompting with split JTs

also
*update test to work with new JT callback call pattern
*fix spelling in template
This commit is contained in:
AlanCoding
2018-10-04 11:50:08 -04:00
parent dccd7f2e9d
commit 475a701f78
4 changed files with 24 additions and 13 deletions

View File

@@ -26,7 +26,7 @@ string of `?all=1` to return all hosts, including disabled ones.
Specify a query string of `?towervars=1` to add variables Specify a query string of `?towervars=1` to add variables
to the hostvars of each host that specifies its enabled state and database ID. to the hostvars of each host that specifies its enabled state and database ID.
Specify a query string of `?subset=shard2of5` to product an inventory that Specify a query string of `?subset=shard2of5` to produce an inventory that
has a restricted number of hosts according to the rules of job splitting. has a restricted number of hosts according to the rules of job splitting.
To apply multiple query strings, join them with the `&` character, like `?hostvars=1&all=1`. To apply multiple query strings, join them with the `&` character, like `?hostvars=1&all=1`.

View File

@@ -342,7 +342,10 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, Notificatio
unallowed_fields = set(kwargs.keys()) - set(fields) unallowed_fields = set(kwargs.keys()) - set(fields)
validated_kwargs = kwargs.copy() validated_kwargs = kwargs.copy()
if unallowed_fields: if unallowed_fields:
logger.warn('Fields {} are not allowed as overrides.'.format(unallowed_fields)) if parent_field_name is None:
logger.warn(six.text_type('Fields {} are not allowed as overrides to spawn {} from {}.').format(
six.text_type(', ').join(unallowed_fields), unified_job, self
))
map(validated_kwargs.pop, unallowed_fields) map(validated_kwargs.pop, unallowed_fields)
unified_job = copy_model_by_class(self, unified_job_class, fields, validated_kwargs) unified_job = copy_model_by_class(self, unified_job_class, fields, validated_kwargs)

View File

@@ -121,7 +121,11 @@ class TaskManager():
spawn_node.save() spawn_node.save()
logger.info('Spawned %s in %s for node %s', job.log_format, workflow_job.log_format, spawn_node.pk) logger.info('Spawned %s in %s for node %s', job.log_format, workflow_job.log_format, spawn_node.pk)
if job._resources_sufficient_for_launch(): if job._resources_sufficient_for_launch():
can_start = job.signal_start() if workflow_job.start_args:
start_args = json.loads(decrypt_field(workflow_job, 'start_args'))
else:
start_args = {}
can_start = job.signal_start(**start_args)
if not can_start: if not can_start:
job.job_explanation = _("Job spawned from workflow could not start because it " job.job_explanation = _("Job spawned from workflow could not start because it "
"was not in the right state or required manual credentials") "was not in the right state or required manual credentials")
@@ -147,7 +151,8 @@ class TaskManager():
if cancel_finished: if cancel_finished:
logger.info('Marking %s as canceled, all spawned jobs have concluded.', workflow_job.log_format) logger.info('Marking %s as canceled, all spawned jobs have concluded.', workflow_job.log_format)
workflow_job.status = 'canceled' workflow_job.status = 'canceled'
workflow_job.save() workflow_job.start_args = '' # blank field to remove encrypted passwords
workflow_job.save(update_fields=['status', 'start_args'])
connection.on_commit(lambda: workflow_job.websocket_emit_status(workflow_job.status)) connection.on_commit(lambda: workflow_job.websocket_emit_status(workflow_job.status))
else: else:
is_done, has_failed = dag.is_workflow_done() is_done, has_failed = dag.is_workflow_done()
@@ -155,8 +160,11 @@ class TaskManager():
continue continue
logger.info('Marking %s as %s.', workflow_job.log_format, 'failed' if has_failed else 'successful') logger.info('Marking %s as %s.', workflow_job.log_format, 'failed' if has_failed else 'successful')
result.append(workflow_job.id) result.append(workflow_job.id)
workflow_job.status = 'failed' if has_failed else 'successful' new_status = 'failed' if has_failed else 'successful'
workflow_job.save() logger.debug(six.text_type("Transitioning {} to {} status.").format(workflow_job.log_format, new_status))
workflow_job.status = new_status
workflow_job.start_args = '' # blank field to remove encrypted passwords
workflow_job.save(update_fields=['status', 'start_args'])
connection.on_commit(lambda: workflow_job.websocket_emit_status(workflow_job.status)) connection.on_commit(lambda: workflow_job.websocket_emit_status(workflow_job.status))
return result return result

View File

@@ -6,7 +6,7 @@ import json
from awx.api.serializers import JobLaunchSerializer from awx.api.serializers import JobLaunchSerializer
from awx.main.models.credential import Credential from awx.main.models.credential import Credential
from awx.main.models.inventory import Inventory, Host from awx.main.models.inventory import Inventory, Host
from awx.main.models.jobs import Job, JobTemplate from awx.main.models.jobs import Job, JobTemplate, UnifiedJobTemplate
from awx.api.versioning import reverse from awx.api.versioning import reverse
@@ -553,15 +553,15 @@ def test_callback_accept_prompted_extra_var(mocker, survey_spec_factory, job_tem
with mocker.patch('awx.main.access.BaseAccess.check_license'): with mocker.patch('awx.main.access.BaseAccess.check_license'):
mock_job = mocker.MagicMock(spec=Job, id=968, extra_vars={"job_launch_var": 3, "survey_var": 4}) mock_job = mocker.MagicMock(spec=Job, id=968, extra_vars={"job_launch_var": 3, "survey_var": 4})
with mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job): with mocker.patch.object(UnifiedJobTemplate, 'create_unified_job', return_value=mock_job):
with mocker.patch('awx.api.serializers.JobSerializer.to_representation', return_value={}): with mocker.patch('awx.api.serializers.JobSerializer.to_representation', return_value={}):
with mocker.patch('awx.api.views.JobTemplateCallback.find_matching_hosts', return_value=[host]): with mocker.patch('awx.api.views.JobTemplateCallback.find_matching_hosts', return_value=[host]):
post( post(
reverse('api:job_template_callback', kwargs={'pk': job_template.pk}), reverse('api:job_template_callback', kwargs={'pk': job_template.pk}),
dict(extra_vars={"job_launch_var": 3, "survey_var": 4}, host_config_key="foo"), dict(extra_vars={"job_launch_var": 3, "survey_var": 4}, host_config_key="foo"),
admin_user, expect=201, format='json') admin_user, expect=201, format='json')
assert JobTemplate.create_unified_job.called assert UnifiedJobTemplate.create_unified_job.called
assert JobTemplate.create_unified_job.call_args == ({ assert UnifiedJobTemplate.create_unified_job.call_args == ({
'extra_vars': {'survey_var': 4, 'job_launch_var': 3}, 'extra_vars': {'survey_var': 4, 'job_launch_var': 3},
'_eager_fields': {'launch_type': 'callback'}, '_eager_fields': {'launch_type': 'callback'},
'limit': 'single-host'}, 'limit': 'single-host'},
@@ -579,15 +579,15 @@ def test_callback_ignore_unprompted_extra_var(mocker, survey_spec_factory, job_t
with mocker.patch('awx.main.access.BaseAccess.check_license'): with mocker.patch('awx.main.access.BaseAccess.check_license'):
mock_job = mocker.MagicMock(spec=Job, id=968, extra_vars={"job_launch_var": 3, "survey_var": 4}) mock_job = mocker.MagicMock(spec=Job, id=968, extra_vars={"job_launch_var": 3, "survey_var": 4})
with mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job): with mocker.patch.object(UnifiedJobTemplate, 'create_unified_job', return_value=mock_job):
with mocker.patch('awx.api.serializers.JobSerializer.to_representation', return_value={}): with mocker.patch('awx.api.serializers.JobSerializer.to_representation', return_value={}):
with mocker.patch('awx.api.views.JobTemplateCallback.find_matching_hosts', return_value=[host]): with mocker.patch('awx.api.views.JobTemplateCallback.find_matching_hosts', return_value=[host]):
post( post(
reverse('api:job_template_callback', kwargs={'pk':job_template.pk}), reverse('api:job_template_callback', kwargs={'pk':job_template.pk}),
dict(extra_vars={"job_launch_var": 3, "survey_var": 4}, host_config_key="foo"), dict(extra_vars={"job_launch_var": 3, "survey_var": 4}, host_config_key="foo"),
admin_user, expect=201, format='json') admin_user, expect=201, format='json')
assert JobTemplate.create_unified_job.called assert UnifiedJobTemplate.create_unified_job.called
assert JobTemplate.create_unified_job.call_args == ({ assert UnifiedJobTemplate.create_unified_job.call_args == ({
'_eager_fields': {'launch_type': 'callback'}, '_eager_fields': {'launch_type': 'callback'},
'limit': 'single-host'}, 'limit': 'single-host'},
) )