mirror of
https://github.com/ansible/awx.git
synced 2026-02-04 02:58:13 -03:30
More job template tests, enable creating a new job by posting to the job template job list.
This commit is contained in:
@@ -511,7 +511,7 @@ class JobTemplateAccess(BaseAccess):
|
||||
def can_change(self, obj, data):
|
||||
'''
|
||||
'''
|
||||
return False # FIXME
|
||||
return self.user.is_superuser # FIXME
|
||||
|
||||
class JobAccess(BaseAccess):
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
|
||||
import os
|
||||
import shlex
|
||||
from django.conf import settings
|
||||
from django.db import models, DatabaseError
|
||||
from django.db.models import CASCADE, SET_NULL, PROTECT
|
||||
@@ -25,7 +26,6 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.timezone import now
|
||||
import exceptions
|
||||
from jsonfield import JSONField
|
||||
from djcelery.models import TaskMeta
|
||||
from rest_framework.authtoken.models import Token
|
||||
@@ -679,6 +679,13 @@ class Job(CommonModel):
|
||||
@property
|
||||
def extra_vars_dict(self):
|
||||
'''Return extra_vars key=value pairs as a dictionary.'''
|
||||
d = {}
|
||||
extra_vars = self.extra_vars.encode('utf-8')
|
||||
for kv in [x.decode('utf-8') for x in shlex.split(extra_vars, posix=True)]:
|
||||
if '=' in kv:
|
||||
k, v = kv.split('=', 1)
|
||||
d[k] = v
|
||||
return d
|
||||
|
||||
@property
|
||||
def celery_task(self):
|
||||
|
||||
@@ -376,6 +376,13 @@ class JobTemplateSerializer(BaseSerializer):
|
||||
res['credential'] = reverse('main:credentials_detail', args=(obj.credential.pk,))
|
||||
return res
|
||||
|
||||
def validate_playbook(self, attrs, source):
|
||||
project = attrs.get('project', None)
|
||||
playbook = attrs.get('playbook', '')
|
||||
if project and playbook and playbook not in project.playbooks:
|
||||
raise serializers.ValidationError('Playbook not found for project')
|
||||
return attrs
|
||||
|
||||
class JobSerializer(BaseSerializer):
|
||||
|
||||
passwords_needed_to_start = serializers.Field(source='get_passwords_needed_to_start')
|
||||
@@ -402,6 +409,28 @@ class JobSerializer(BaseSerializer):
|
||||
res['job_template'] = reverse('main:job_template_detail', args=(obj.job_template.pk,))
|
||||
return res
|
||||
|
||||
def from_native(self, data, files):
|
||||
# When creating a new job and a job template is specified, populate any
|
||||
# fields not provided in data from the job template.
|
||||
if not self.object and isinstance(data, dict) and 'job_template' in data:
|
||||
try:
|
||||
job_template = JobTemplate.objects.get(pk=data['job_template'])
|
||||
except JobTemplate.DoesNotExist:
|
||||
self._errors = {'job_template': 'Invalid job template'}
|
||||
return
|
||||
# Don't auto-populate name or description.
|
||||
data.setdefault('job_type', job_template.job_type)
|
||||
data.setdefault('inventory', job_template.inventory.pk)
|
||||
data.setdefault('project', job_template.project.pk)
|
||||
data.setdefault('playbook', job_template.playbook)
|
||||
if job_template.credential:
|
||||
data.setdefault('credential', job_template.credential.pk)
|
||||
data.setdefault('forks', job_template.forks)
|
||||
data.setdefault('limit', job_template.limit)
|
||||
data.setdefault('verbosity', job_template.verbosity)
|
||||
data.setdefault('extra_vars', job_template.extra_vars)
|
||||
return super(JobSerializer, self).from_native(data, files)
|
||||
|
||||
class JobHostSummarySerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
|
||||
@@ -55,7 +55,10 @@ class BaseTestMixin(object):
|
||||
username = user_or_username
|
||||
password = password or self._user_passwords.get(username)
|
||||
previous_auth = self._current_auth
|
||||
self._current_auth = (username, password)
|
||||
if username is None:
|
||||
self._current_auth = None
|
||||
else:
|
||||
self._current_auth = (username, password)
|
||||
yield
|
||||
finally:
|
||||
self._current_auth = previous_auth
|
||||
@@ -179,8 +182,8 @@ class BaseTestMixin(object):
|
||||
assert response.status_code == expect, "expected status %s, got %s for url=%s as auth=%s: %s" % (expect, response.status_code, url, auth, response.content)
|
||||
if method_name == 'head':
|
||||
self.assertFalse(response.content)
|
||||
if response.status_code not in [ 202, 204, 400, 405, 409 ] and method_name != 'head':
|
||||
# no JSON responses in these at least for now, 400/409 should probably return some (FIXME)
|
||||
if response.status_code not in [ 202, 204, 405, 409 ] and method_name != 'head' and response.content:
|
||||
# no JSON responses in these at least for now, 409 should probably return some (FIXME)
|
||||
return json.loads(response.content)
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -317,7 +317,11 @@ class BaseJobTest(BaseTest):
|
||||
project=self.proj_dev,
|
||||
playbook=self.proj_dev.playbooks[0],
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
)
|
||||
self.job_eng_check = self.jt_eng_check.create_job(
|
||||
created_by=self.user_sue,
|
||||
credential=self.cred_doug,
|
||||
)
|
||||
self.jt_eng_run = JobTemplate.objects.create(
|
||||
name='eng-dev-run',
|
||||
job_type='run',
|
||||
@@ -326,6 +330,10 @@ class BaseJobTest(BaseTest):
|
||||
playbook=self.proj_dev.playbooks[0],
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
self.job_eng_run = self.jt_eng_run.create_job(
|
||||
created_by=self.user_sue,
|
||||
credential=self.cred_chuck,
|
||||
)
|
||||
|
||||
# Support has job templates to check/run the test project onto
|
||||
# their own inventory.
|
||||
@@ -336,7 +344,11 @@ class BaseJobTest(BaseTest):
|
||||
project=self.proj_test,
|
||||
playbook=self.proj_test.playbooks[0],
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
)
|
||||
self.job_sup_check = self.jt_sup_check.create_job(
|
||||
created_by=self.user_sue,
|
||||
credential=self.cred_frank,
|
||||
)
|
||||
self.jt_sup_run = JobTemplate.objects.create(
|
||||
name='sup-test-run',
|
||||
job_type='run',
|
||||
@@ -345,6 +357,10 @@ class BaseJobTest(BaseTest):
|
||||
playbook=self.proj_test.playbooks[0],
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
self.job_sup_run = self.jt_sup_run.create_job(
|
||||
created_by=self.user_sue,
|
||||
credential=self.cred_eve,
|
||||
)
|
||||
|
||||
# Operations has job templates to check/run the prod project onto
|
||||
# both east and west inventories, by default using the team credential.
|
||||
@@ -356,7 +372,10 @@ class BaseJobTest(BaseTest):
|
||||
playbook=self.proj_prod.playbooks[0],
|
||||
credential=self.cred_ops_east,
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
)
|
||||
self.job_ops_east_check = self.jt_ops_east_check.create_job(
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
self.jt_ops_east_run = JobTemplate.objects.create(
|
||||
name='ops-east-prod-run',
|
||||
job_type='run',
|
||||
@@ -366,6 +385,9 @@ class BaseJobTest(BaseTest):
|
||||
credential=self.cred_ops_east,
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
self.job_ops_east_run = self.jt_ops_east_run.create_job(
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
self.jt_ops_west_check = JobTemplate.objects.create(
|
||||
name='ops-west-prod-check',
|
||||
job_type='check',
|
||||
@@ -374,7 +396,10 @@ class BaseJobTest(BaseTest):
|
||||
playbook=self.proj_prod.playbooks[0],
|
||||
credential=self.cred_ops_west,
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
)
|
||||
self.job_ops_west_check = self.jt_ops_west_check.create_job(
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
self.jt_ops_west_run = JobTemplate.objects.create(
|
||||
name='ops-west-prod-run',
|
||||
job_type='run',
|
||||
@@ -384,6 +409,9 @@ class BaseJobTest(BaseTest):
|
||||
credential=self.cred_ops_west,
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
self.job_ops_west_run = self.jt_ops_west_run.create_job(
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super(BaseJobTest, self).setUp()
|
||||
@@ -395,19 +423,23 @@ class JobTemplateTest(BaseJobTest):
|
||||
def setUp(self):
|
||||
super(JobTemplateTest, self).setUp()
|
||||
|
||||
def _test_invalid_creds(self, url, data=None, methods=None):
|
||||
data = data or {}
|
||||
methods = methods or ('options', 'head', 'get')
|
||||
for auth in [(None,), ('invalid', 'password')]:
|
||||
with self.current_user(*auth):
|
||||
for method in methods:
|
||||
f = getattr(self, method)
|
||||
if method in ('post', 'put'):
|
||||
f(url, data, expect=401)
|
||||
else:
|
||||
f(url, expect=401)
|
||||
|
||||
def test_get_job_template_list(self):
|
||||
url = reverse('main:job_template_list')
|
||||
|
||||
# no credentials == 401
|
||||
self.options(url, expect=401)
|
||||
self.head(url, expect=401)
|
||||
self.get(url, expect=401)
|
||||
|
||||
# wrong credentials == 401
|
||||
with self.current_user('invalid', 'password'):
|
||||
self.options(url, expect=401)
|
||||
self.head(url, expect=401)
|
||||
self.get(url, expect=401)
|
||||
# Test with no auth and with invalid login.
|
||||
self._test_invalid_creds(url)
|
||||
|
||||
# sue's credentials (superuser) == 200, full list
|
||||
with self.current_user(self.user_sue):
|
||||
@@ -418,7 +450,7 @@ class JobTemplateTest(BaseJobTest):
|
||||
self.check_pagination_and_size(response, qs.count())
|
||||
self.check_list_ids(response, qs)
|
||||
|
||||
# FIXME: Check individual job template result.
|
||||
# FIXME: Check individual job template result fields.
|
||||
|
||||
# alex's credentials (admin of all orgs) == 200, full list
|
||||
with self.current_user(self.user_alex):
|
||||
@@ -441,80 +473,132 @@ class JobTemplateTest(BaseJobTest):
|
||||
#self.check_pagination_and_size(response, qs.count())
|
||||
#self.check_list_ids(response, qs)
|
||||
|
||||
# FIXME: Check with other credentials.
|
||||
|
||||
def test_post_job_template_list(self):
|
||||
url = reverse('main:job_template_list')
|
||||
|
||||
return # FIXME
|
||||
|
||||
# org admin can add job template
|
||||
data = dict(
|
||||
name = 'job-foo',
|
||||
credential = self.user_credential.pk,
|
||||
inventory = self.inventory.pk,
|
||||
project = self.project.pk,
|
||||
name = 'new job template',
|
||||
job_type = PERM_INVENTORY_DEPLOY,
|
||||
playbook = self.project.playbooks[0],
|
||||
inventory = self.inv_eng.pk,
|
||||
project = self.proj_dev.pk,
|
||||
playbook = self.proj_dev.playbooks[0],
|
||||
)
|
||||
with self.current_user(self.normal_django_user):
|
||||
|
||||
# Test with no auth and with invalid login.
|
||||
self._test_invalid_creds(url, data, methods=('post',))
|
||||
|
||||
# sue can always add job templates.
|
||||
with self.current_user(self.user_sue):
|
||||
response = self.post(url, data, expect=201)
|
||||
detail_url = reverse('main:job_template_detail',
|
||||
args=(response['id'],))
|
||||
self.assertEquals(response['url'], detail_url)
|
||||
|
||||
# other_django_user is on a team that can deploy, so can create both
|
||||
# deploy and check type job templates
|
||||
with self.current_user(self.other_django_user):
|
||||
data['name'] = 'job-foo2'
|
||||
response = self.post(url, data, expect=201)
|
||||
data['name'] = 'job-foo3'
|
||||
data['job_type'] = PERM_INVENTORY_CHECK
|
||||
response = self.post(url, data, expect=201)
|
||||
# Check that all fields provided were set.
|
||||
jt = JobTemplate.objects.get(pk=response['id'])
|
||||
self.assertEqual(jt.name, data['name'])
|
||||
self.assertEqual(jt.job_type, data['job_type'])
|
||||
self.assertEqual(jt.inventory.pk, data['inventory'])
|
||||
self.assertEqual(jt.credential, None)
|
||||
self.assertEqual(jt.project.pk, data['project'])
|
||||
self.assertEqual(jt.playbook, data['playbook'])
|
||||
|
||||
# other2_django_user has individual permissions to run check mode,
|
||||
# but not deploy
|
||||
with self.current_user(self.other2_django_user):
|
||||
data['name'] = 'job-foo4'
|
||||
#data['credential'] = self.user_credential.pk
|
||||
#response = self.post(url, data, expect=201)
|
||||
data['name'] = 'job-foo5'
|
||||
data['job_type'] = PERM_INVENTORY_DEPLOY
|
||||
response = self.post(url, data, expect=403)
|
||||
# Test that all required fields are really required.
|
||||
data['name'] = 'another new job template'
|
||||
for field in ('name', 'job_type', 'inventory', 'project', 'playbook'):
|
||||
with self.current_user(self.user_sue):
|
||||
d = dict(data.items())
|
||||
d.pop(field)
|
||||
response = self.post(url, d, expect=400)
|
||||
self.assertTrue(field in response,
|
||||
'no error for field "%s" in response' % field)
|
||||
|
||||
# nobody user can't even run check mode
|
||||
with self.current_user(self.nobody_django_user):
|
||||
data['name'] = 'job-foo5'
|
||||
data['job_type'] = PERM_INVENTORY_CHECK
|
||||
response = self.post(url, data, expect=403)
|
||||
data['job_type'] = PERM_INVENTORY_DEPLOY
|
||||
response = self.post(url, data, expect=403)
|
||||
# Test invalid value for job_type.
|
||||
with self.current_user(self.user_sue):
|
||||
d = dict(data.items())
|
||||
d['job_type'] = 'world domination'
|
||||
response = self.post(url, d, expect=400)
|
||||
self.assertTrue('job_type' in response)
|
||||
|
||||
# Test playbook not in list of project playbooks.
|
||||
with self.current_user(self.user_sue):
|
||||
d = dict(data.items())
|
||||
d['playbook'] = 'no_playbook_here.yml'
|
||||
response = self.post(url, d, expect=400)
|
||||
self.assertTrue('playbook' in response)
|
||||
|
||||
# FIXME: Check other credentials and optional fields.
|
||||
|
||||
def test_get_job_template_detail(self):
|
||||
|
||||
return # FIXME
|
||||
|
||||
url = reverse('main:job_template_detail', args=(self.job_template1.pk,))
|
||||
|
||||
# verify we can also get the job template record
|
||||
with self.current_user(self.other2_django_user):
|
||||
jt = self.jt_eng_run
|
||||
url = reverse('main:job_template_detail', args=(jt.pk,))
|
||||
|
||||
# Test with no auth and with invalid login.
|
||||
self._test_invalid_creds(url)
|
||||
|
||||
# sue can read the job template detail.
|
||||
with self.current_user(self.user_sue):
|
||||
self.options(url)
|
||||
self.head(url)
|
||||
response = self.get(url)
|
||||
self.assertEqual(response['url'], url)
|
||||
|
||||
# FIXME: Check other credentials and optional fields.
|
||||
|
||||
# TODO: add more tests that show
|
||||
# the method used to START a JobTemplate follow the exact same permissions as those to create it ...
|
||||
# and that jobs come back nicely serialized with related resources and so on ...
|
||||
# that we can drill all the way down and can get at host failure lists, etc ...
|
||||
|
||||
def test_put_job_template_detail(self):
|
||||
pass
|
||||
jt = self.jt_eng_run
|
||||
url = reverse('main:job_template_detail', args=(jt.pk,))
|
||||
|
||||
# Test with no auth and with invalid login.
|
||||
self._test_invalid_creds(url, methods=('put',))
|
||||
|
||||
# sue can update the job template detail.
|
||||
with self.current_user(self.user_sue):
|
||||
data = self.get(url)
|
||||
response = self.put(url, data)
|
||||
|
||||
# FIXME: Check other credentials and optional fields.
|
||||
|
||||
def test_get_job_template_job_list(self):
|
||||
pass
|
||||
jt = self.jt_eng_run
|
||||
url = reverse('main:job_template_job_list', args=(jt.pk,))
|
||||
|
||||
# Test with no auth and with invalid login.
|
||||
self._test_invalid_creds(url)
|
||||
|
||||
# sue can read the job template job list.
|
||||
with self.current_user(self.user_sue):
|
||||
self.options(url)
|
||||
self.head(url)
|
||||
response = self.get(url)
|
||||
qs = jt.jobs.all()
|
||||
self.check_pagination_and_size(response, qs.count())
|
||||
self.check_list_ids(response, qs)
|
||||
|
||||
# FIXME: Check other credentials and optional fields.
|
||||
|
||||
def test_post_job_template_job_list(self):
|
||||
pass
|
||||
jt = self.jt_eng_run
|
||||
url = reverse('main:job_template_job_list', args=(jt.pk,))
|
||||
data = dict(
|
||||
name='new job from template',
|
||||
credential=self.cred_bob.pk,
|
||||
)
|
||||
|
||||
# Test with no auth and with invalid login.
|
||||
self._test_invalid_creds(url, data, methods=('post',))
|
||||
|
||||
# sue can create a new job from the template.
|
||||
with self.current_user(self.user_sue):
|
||||
response = self.post(url, data, expect=201)
|
||||
|
||||
# FIXME: Check other credentials and optional fields.
|
||||
|
||||
class JobTest(BaseJobTest):
|
||||
|
||||
@@ -533,7 +617,16 @@ class JobTest(BaseJobTest):
|
||||
def test_put_job_detail(self):
|
||||
pass
|
||||
|
||||
def test_post_job_detail(self):
|
||||
def test_get_job_start(self):
|
||||
pass
|
||||
|
||||
def test_post_job_start(self):
|
||||
pass
|
||||
|
||||
def test_get_job_cancel(self):
|
||||
pass
|
||||
|
||||
def test_post_job_cancel(self):
|
||||
pass
|
||||
|
||||
def test_get_job_host_list(self):
|
||||
|
||||
@@ -116,6 +116,8 @@ job_templates_urls = patterns('lib.main.views',
|
||||
jobs_urls = patterns('lib.main.views',
|
||||
url(r'^$', 'job_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'job_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/start/$', 'job_start'),
|
||||
url(r'^(?P<pk>[0-9]+)/cancel/$', 'job_cancel'),
|
||||
url(r'^(?P<pk>[0-9]+)/hosts/$', 'job_hosts_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/successful_hosts/$', 'jobs_successful_hosts_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/changed_hosts/$', 'jobs_changed_hosts_list'),
|
||||
|
||||
@@ -913,7 +913,6 @@ class JobTemplateList(BaseList):
|
||||
def _get_queryset(self):
|
||||
return get_user_queryset(self.request.user, self.model)
|
||||
|
||||
|
||||
class JobTemplateDetail(BaseDetail):
|
||||
|
||||
model = JobTemplate
|
||||
@@ -955,6 +954,13 @@ class JobDetail(BaseDetail):
|
||||
serializer_class = JobSerializer
|
||||
permission_classes = (CustomRbac,)
|
||||
|
||||
class JobStart(BaseDetail):
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
pass # FIXME
|
||||
|
||||
class JobCancel(BaseDetail):
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
pass # FIXME
|
||||
|
||||
|
||||
Reference in New Issue
Block a user