mirror of
https://github.com/ansible/awx.git
synced 2026-05-08 01:47:35 -02:30
Implement /launch endpoint on job template
Partial implementation of: https://trello.com/c/7uXHs7ze/14-require-admin-to-be-able-to-run-a-job-without-a-job-template
This commit is contained in:
@@ -130,6 +130,7 @@ permission_urls = patterns('awx.api.views',
|
|||||||
job_template_urls = patterns('awx.api.views',
|
job_template_urls = patterns('awx.api.views',
|
||||||
url(r'^$', 'job_template_list'),
|
url(r'^$', 'job_template_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', 'job_template_detail'),
|
url(r'^(?P<pk>[0-9]+)/$', 'job_template_detail'),
|
||||||
|
url(r'^(?P<pk>[0-9]+)launch/$', 'job_template_launch'),
|
||||||
url(r'^(?P<pk>[0-9]+)/jobs/$', 'job_template_jobs_list'),
|
url(r'^(?P<pk>[0-9]+)/jobs/$', 'job_template_jobs_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/callback/$', 'job_template_callback'),
|
url(r'^(?P<pk>[0-9]+)/callback/$', 'job_template_callback'),
|
||||||
url(r'^(?P<pk>[0-9]+)/schedules/$', 'job_template_schedules_list'),
|
url(r'^(?P<pk>[0-9]+)/schedules/$', 'job_template_schedules_list'),
|
||||||
|
|||||||
@@ -1337,6 +1337,31 @@ class JobTemplateDetail(RetrieveUpdateDestroyAPIView):
|
|||||||
model = JobTemplate
|
model = JobTemplate
|
||||||
serializer_class = JobTemplateSerializer
|
serializer_class = JobTemplateSerializer
|
||||||
|
|
||||||
|
class JobTemplateLaunch(GenericAPIView):
|
||||||
|
|
||||||
|
model = JobTemplate
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
obj = self.get_object()
|
||||||
|
data = {}
|
||||||
|
data['can_start_without_user_input'] = obj.can_start_without_user_input()
|
||||||
|
data['passwords_needed_to_start'] = obj.passwords_needed_to_start
|
||||||
|
data['ask_variables_on_launch'] = obj.ask_variables_on_launch
|
||||||
|
return Response(data)
|
||||||
|
|
||||||
|
def post(self, request, *args, **obj):
|
||||||
|
obj = self.get_object()
|
||||||
|
if not request.user.can_access(self.model, 'start', obj):
|
||||||
|
raise PermissionDenied()
|
||||||
|
new_job = obj.create_unified_job()
|
||||||
|
result = new_job.signal_start(**request.DATA)
|
||||||
|
if not result:
|
||||||
|
data = dict(passwords_needed_to_start=obj.passwords_needed_to_start)
|
||||||
|
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
else:
|
||||||
|
data = dict(job=new_job.id)
|
||||||
|
return Response(data, status=status.HTTP_202_ACCEPTED)
|
||||||
|
|
||||||
class JobTemplateSchedulesList(SubListCreateAPIView):
|
class JobTemplateSchedulesList(SubListCreateAPIView):
|
||||||
|
|
||||||
view_name = "Job Template Schedules"
|
view_name = "Job Template Schedules"
|
||||||
|
|||||||
@@ -920,6 +920,26 @@ class JobTemplateAccess(BaseAccess):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def can_start(self, obj):
|
||||||
|
reader = TaskSerializer()
|
||||||
|
validation_info = reader.from_file()
|
||||||
|
|
||||||
|
if 'test' in sys.argv or 'jenkins' in sys.argv:
|
||||||
|
validation_info['free_instances'] = 99999999
|
||||||
|
validation_info['time_remaining'] = 99999999
|
||||||
|
validation_info['grace_period_remaining'] = 99999999
|
||||||
|
|
||||||
|
if validation_info.get('time_remaining', None) is None:
|
||||||
|
raise PermissionDenied("license is missing")
|
||||||
|
if validation_info.get("grace_period_remaining") <= 0:
|
||||||
|
raise PermissionDenied("license has expired")
|
||||||
|
if validation_info.get('free_instances', 0) < 0:
|
||||||
|
raise PermissionDenied("Host Count exceeds available instances")
|
||||||
|
|
||||||
|
dep_access = self.user.can_access(Inventory, 'read', obj.inventory) and \
|
||||||
|
self.user.can_access(Project, 'read', obj.project)
|
||||||
|
return self.can_read(obj) and dep_access
|
||||||
|
|
||||||
def can_change(self, obj, data):
|
def can_change(self, obj, data):
|
||||||
return self.can_read(obj) and self.can_add(data)
|
return self.can_read(obj) and self.can_add(data)
|
||||||
|
|
||||||
|
|||||||
@@ -189,6 +189,18 @@ class JobTemplate(UnifiedJobTemplate, JobOptions):
|
|||||||
needed.append(pw)
|
needed.append(pw)
|
||||||
return bool(self.credential and not len(needed))
|
return bool(self.credential and not len(needed))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def passwords_needed_to_start(self):
|
||||||
|
'''Return list of password field names needed to start the job.'''
|
||||||
|
needed = []
|
||||||
|
if self.credential:
|
||||||
|
for pw in self.credential.passwords_needed:
|
||||||
|
if pw == 'password':
|
||||||
|
needed.append('ssh_password')
|
||||||
|
else:
|
||||||
|
needed.append(pw)
|
||||||
|
return needed
|
||||||
|
|
||||||
def _can_update(self):
|
def _can_update(self):
|
||||||
return self.can_start_without_user_input()
|
return self.can_start_without_user_input()
|
||||||
|
|
||||||
|
|||||||
@@ -302,7 +302,7 @@ class BaseTestMixin(object):
|
|||||||
self.assertFalse(response.content)
|
self.assertFalse(response.content)
|
||||||
#if return_response_object:
|
#if return_response_object:
|
||||||
# return response
|
# return response
|
||||||
if response.status_code not in [ 202, 204, 405 ] and method_name != 'head' and response.content:
|
if response.status_code not in [ 204, 405 ] and method_name != 'head' and response.content:
|
||||||
# no JSON responses in these at least for now, 409 should probably return some (FIXME)
|
# no JSON responses in these at least for now, 409 should probably return some (FIXME)
|
||||||
if response['Content-Type'].startswith('application/json'):
|
if response['Content-Type'].startswith('application/json'):
|
||||||
obj = json.loads(response.content)
|
obj = json.loads(response.content)
|
||||||
|
|||||||
@@ -681,6 +681,32 @@ class JobTemplateTest(BaseJobTestMixin, django.test.TestCase):
|
|||||||
|
|
||||||
# FIXME: Check other credentials and optional fields.
|
# FIXME: Check other credentials and optional fields.
|
||||||
|
|
||||||
|
def test_launch_job_template(self):
|
||||||
|
url = reverse('api:job_template_list')
|
||||||
|
data = dict(
|
||||||
|
name = 'launched job template',
|
||||||
|
job_type = PERM_INVENTORY_DEPLOY,
|
||||||
|
inventory = self.inv_eng.pk,
|
||||||
|
project = self.proj_dev.pk,
|
||||||
|
playbook = self.proj_dev.playbooks[0],
|
||||||
|
)
|
||||||
|
with self.current_user(self.user_sue):
|
||||||
|
response = self.post(url, data, expect=201)
|
||||||
|
detail_url = reverse('api:job_template_detail',
|
||||||
|
args=(response['id'],))
|
||||||
|
self.assertEquals(response['url'], detail_url)
|
||||||
|
|
||||||
|
launch_url = reverse('api:job_template_launch',
|
||||||
|
args=(response['id'],))
|
||||||
|
|
||||||
|
# Invalid auth can't trigger the launch endpoint
|
||||||
|
self.check_invalid_auth(launch_url, {}, methods=('post',))
|
||||||
|
|
||||||
|
with self.current_user(self.user_sue):
|
||||||
|
response = self.post(launch_url, {}, expect=202)
|
||||||
|
j = Job.objects.get(pk=response['job'])
|
||||||
|
self.assertTrue(j.status == 'new')
|
||||||
|
|
||||||
class JobTest(BaseJobTestMixin, django.test.TestCase):
|
class JobTest(BaseJobTestMixin, django.test.TestCase):
|
||||||
|
|
||||||
def test_get_job_list(self):
|
def test_get_job_list(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user