From 0d98a1980ef4dd186491d98495221085fe58def3 Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Tue, 7 Jan 2020 10:12:47 -0500 Subject: [PATCH] Add a configurable limit for job forks --- awx/main/conf.py | 11 +++++++++++ awx/main/models/jobs.py | 6 ++++++ awx/main/tasks.py | 8 ++++++-- .../tests/functional/api/test_job_template.py | 16 ++++++++++++++++ .../forms/jobs-form/configuration-jobs.form.js | 4 ++++ awx/ui/client/src/shared/Utilities.js | 2 +- 6 files changed, 44 insertions(+), 3 deletions(-) diff --git a/awx/main/conf.py b/awx/main/conf.py index 33ed4d9e13..5a42535ca2 100644 --- a/awx/main/conf.py +++ b/awx/main/conf.py @@ -615,6 +615,17 @@ register( category=_('Jobs'), category_slug='jobs', ) +register( + 'MAX_FORKS', + field_class=fields.IntegerField, + allow_null=False, + default=0, + label=_('Maximum number of forks per job.'), + help_text=_('Saving a Job Template with more than this number of forks will result in an error. ' + 'When set to 0 (the default), no limit is applied.'), + category=_('Jobs'), + category_slug='jobs', +) register( 'LOG_AGGREGATOR_HOST', diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 4048cb1358..819defc4f8 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -13,6 +13,7 @@ from urllib.parse import urljoin # Django from django.conf import settings +from django.core.exceptions import ValidationError from django.db import models #from django.core.cache import cache from django.utils.encoding import smart_str @@ -293,6 +294,11 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, Resour def resources_needed_to_start(self): return [fd for fd in ['project', 'inventory'] if not getattr(self, '{}_id'.format(fd))] + def clean_forks(self): + if settings.MAX_FORKS > 0 and self.forks > settings.MAX_FORKS: + raise ValidationError(_(f'Maximum number of forks ({settings.MAX_FORKS}) exceeded.')) + return self.forks + def create_job(self, **kwargs): ''' Create a new job based on this template. diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 09dc866d30..785ed2d655 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -1656,8 +1656,12 @@ class RunJob(BaseTask): args.append('--vault-id') args.append('{}@prompt'.format(vault_id)) - if job.forks: # FIXME: Max limit? - args.append('--forks=%d' % job.forks) + if job.forks: + if settings.MAX_FORKS > 0 and job.forks > settings.MAX_FORKS: + logger.warning(f'Maximum number of forks ({settings.MAX_FORKS}) exceeded.') + args.append('--forks=%d' % settings.MAX_FORKS) + else: + args.append('--forks=%d' % job.forks) if job.force_handlers: args.append('--force-handlers') if job.limit: diff --git a/awx/main/tests/functional/api/test_job_template.py b/awx/main/tests/functional/api/test_job_template.py index 6ae9e87d7e..8ad864ee8e 100644 --- a/awx/main/tests/functional/api/test_job_template.py +++ b/awx/main/tests/functional/api/test_job_template.py @@ -118,6 +118,22 @@ def test_extra_credential_unique_type_xfail(get, post, organization_factory, job assert response.data.get('count') == 1 +@pytest.mark.django_db +def test_create_with_forks_exceeding_maximum_xfail(alice, post, project, inventory, settings): + project.use_role.members.add(alice) + inventory.use_role.members.add(alice) + settings.MAX_FORKS = 10 + response = post(reverse('api:job_template_list'), { + 'name': 'Some name', + 'project': project.id, + 'inventory': inventory.id, + 'playbook': 'helloworld.yml', + 'forks': 11, + }, alice) + assert response.status_code == 400 + assert 'Maximum number of forks (10) exceeded' in str(response.data) + + @pytest.mark.django_db def test_attach_extra_credential(get, post, organization_factory, job_template_factory, credential): objs = organization_factory("org", superusers=['admin']) diff --git a/awx/ui/client/src/configuration/forms/jobs-form/configuration-jobs.form.js b/awx/ui/client/src/configuration/forms/jobs-form/configuration-jobs.form.js index ab3aa7404c..3d0ad107ed 100644 --- a/awx/ui/client/src/configuration/forms/jobs-form/configuration-jobs.form.js +++ b/awx/ui/client/src/configuration/forms/jobs-form/configuration-jobs.form.js @@ -58,6 +58,10 @@ export default ['i18n', function(i18n) { type: 'text', reset: 'ANSIBLE_FACT_CACHE_TIMEOUT', }, + MAX_FORKS: { + type: 'text', + reset: 'MAX_FORKS', + }, PROJECT_UPDATE_VVV: { type: 'toggleSwitch', }, diff --git a/awx/ui/client/src/shared/Utilities.js b/awx/ui/client/src/shared/Utilities.js index 931553dac6..aa019bcbc3 100644 --- a/awx/ui/client/src/shared/Utilities.js +++ b/awx/ui/client/src/shared/Utilities.js @@ -233,7 +233,7 @@ angular.module('Utilities', ['RestServices', 'Utilities']) addApiErrors(form.fields[field], field); } } - if (defaultMsg) { + if (!fieldErrors && defaultMsg) { Alert(defaultMsg.hdr, defaultMsg.msg); } } else if (typeof data === 'object' && data !== null) {