diff --git a/awx/api/templates/api/inventory_script_view.md b/awx/api/templates/api/inventory_script_view.md index 19cfff28ce..c1315bfc94 100644 --- a/awx/api/templates/api/inventory_script_view.md +++ b/awx/api/templates/api/inventory_script_view.md @@ -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 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. To apply multiple query strings, join them with the `&` character, like `?hostvars=1&all=1`. diff --git a/awx/main/models/unified_jobs.py b/awx/main/models/unified_jobs.py index cbda5109e2..b416bd8616 100644 --- a/awx/main/models/unified_jobs.py +++ b/awx/main/models/unified_jobs.py @@ -342,7 +342,10 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, Notificatio unallowed_fields = set(kwargs.keys()) - set(fields) validated_kwargs = kwargs.copy() 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) unified_job = copy_model_by_class(self, unified_job_class, fields, validated_kwargs) diff --git a/awx/main/scheduler/task_manager.py b/awx/main/scheduler/task_manager.py index 540b94181e..863352857c 100644 --- a/awx/main/scheduler/task_manager.py +++ b/awx/main/scheduler/task_manager.py @@ -121,7 +121,11 @@ class TaskManager(): spawn_node.save() 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(): - 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: job.job_explanation = _("Job spawned from workflow could not start because it " "was not in the right state or required manual credentials") @@ -147,7 +151,8 @@ class TaskManager(): if cancel_finished: logger.info('Marking %s as canceled, all spawned jobs have concluded.', workflow_job.log_format) 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)) else: is_done, has_failed = dag.is_workflow_done() @@ -155,8 +160,11 @@ class TaskManager(): continue logger.info('Marking %s as %s.', workflow_job.log_format, 'failed' if has_failed else 'successful') result.append(workflow_job.id) - workflow_job.status = 'failed' if has_failed else 'successful' - workflow_job.save() + new_status = 'failed' if has_failed else 'successful' + 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)) return result diff --git a/awx/main/tests/functional/api/test_job_runtime_params.py b/awx/main/tests/functional/api/test_job_runtime_params.py index cf9c5c8286..4bd46e5cdf 100644 --- a/awx/main/tests/functional/api/test_job_runtime_params.py +++ b/awx/main/tests/functional/api/test_job_runtime_params.py @@ -6,7 +6,7 @@ import json from awx.api.serializers import JobLaunchSerializer from awx.main.models.credential import Credential 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 @@ -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'): 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.views.JobTemplateCallback.find_matching_hosts', return_value=[host]): post( reverse('api:job_template_callback', kwargs={'pk': job_template.pk}), dict(extra_vars={"job_launch_var": 3, "survey_var": 4}, host_config_key="foo"), admin_user, expect=201, format='json') - assert JobTemplate.create_unified_job.called - assert JobTemplate.create_unified_job.call_args == ({ + assert UnifiedJobTemplate.create_unified_job.called + assert UnifiedJobTemplate.create_unified_job.call_args == ({ 'extra_vars': {'survey_var': 4, 'job_launch_var': 3}, '_eager_fields': {'launch_type': 'callback'}, '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'): 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.views.JobTemplateCallback.find_matching_hosts', return_value=[host]): post( reverse('api:job_template_callback', kwargs={'pk':job_template.pk}), dict(extra_vars={"job_launch_var": 3, "survey_var": 4}, host_config_key="foo"), admin_user, expect=201, format='json') - assert JobTemplate.create_unified_job.called - assert JobTemplate.create_unified_job.call_args == ({ + assert UnifiedJobTemplate.create_unified_job.called + assert UnifiedJobTemplate.create_unified_job.call_args == ({ '_eager_fields': {'launch_type': 'callback'}, 'limit': 'single-host'}, )