Merge pull request #9 from AlanCoding/11th-hour

Merge devel, update job launch behavior to newest permissions stuff
This commit is contained in:
Akita Noek
2016-04-18 15:03:58 -04:00
16 changed files with 639 additions and 101 deletions

View File

@@ -1675,7 +1675,9 @@ class JobTemplateSerializer(UnifiedJobTemplateSerializer, JobOptionsSerializer):
class Meta: class Meta:
model = JobTemplate model = JobTemplate
fields = ('*', 'host_config_key', 'ask_variables_on_launch', 'survey_enabled', 'become_enabled') fields = ('*', 'host_config_key', 'ask_variables_on_launch', 'ask_limit_on_launch',
'ask_tags_on_launch', 'ask_job_type_on_launch', 'ask_inventory_on_launch',
'ask_credential_on_launch', 'survey_enabled', 'become_enabled')
def get_related(self, obj): def get_related(self, obj):
res = super(JobTemplateSerializer, self).get_related(obj) res = super(JobTemplateSerializer, self).get_related(obj)
@@ -1732,10 +1734,16 @@ class JobSerializer(UnifiedJobSerializer, JobOptionsSerializer):
passwords_needed_to_start = serializers.ReadOnlyField() passwords_needed_to_start = serializers.ReadOnlyField()
ask_variables_on_launch = serializers.ReadOnlyField() ask_variables_on_launch = serializers.ReadOnlyField()
ask_limit_on_launch = serializers.ReadOnlyField()
ask_tags_on_launch = serializers.ReadOnlyField()
ask_job_type_on_launch = serializers.ReadOnlyField()
ask_inventory_on_launch = serializers.ReadOnlyField()
class Meta: class Meta:
model = Job model = Job
fields = ('*', 'job_template', 'passwords_needed_to_start', 'ask_variables_on_launch') fields = ('*', 'job_template', 'passwords_needed_to_start', 'ask_variables_on_launch',
'ask_limit_on_launch', 'ask_tags_on_launch', 'ask_job_type_on_launch',
'ask_inventory_on_launch')
def get_related(self, obj): def get_related(self, obj):
res = super(JobSerializer, self).get_related(obj) res = super(JobSerializer, self).get_related(obj)
@@ -2098,24 +2106,36 @@ class JobLaunchSerializer(BaseSerializer):
can_start_without_user_input = serializers.BooleanField(read_only=True) can_start_without_user_input = serializers.BooleanField(read_only=True)
variables_needed_to_start = serializers.ReadOnlyField() variables_needed_to_start = serializers.ReadOnlyField()
credential_needed_to_start = serializers.SerializerMethodField() credential_needed_to_start = serializers.SerializerMethodField()
inventory_needed_to_start = serializers.SerializerMethodField()
survey_enabled = serializers.SerializerMethodField() survey_enabled = serializers.SerializerMethodField()
extra_vars = VerbatimField(required=False, write_only=True) extra_vars = VerbatimField(required=False, write_only=True)
class Meta: class Meta:
model = JobTemplate model = JobTemplate
fields = ('can_start_without_user_input', 'passwords_needed_to_start', 'extra_vars', fields = ('can_start_without_user_input', 'passwords_needed_to_start',
'ask_variables_on_launch', 'survey_enabled', 'variables_needed_to_start', 'extra_vars', 'limit', 'job_tags', 'skip_tags', 'job_type', 'inventory',
'credential', 'credential_needed_to_start',) 'credential', 'ask_variables_on_launch', 'ask_tags_on_launch',
read_only_fields = ('ask_variables_on_launch',) 'ask_job_type_on_launch', 'ask_inventory_on_launch', 'ask_limit_on_launch',
'survey_enabled', 'variables_needed_to_start',
'credential_needed_to_start', 'inventory_needed_to_start',)
read_only_fields = ('ask_variables_on_launch', 'ask_limit_on_launch',
'ask_tags_on_launch', 'ask_job_type_on_launch',
'ask_inventory_on_launch', 'ask_credential_on_launch')
extra_kwargs = { extra_kwargs = {
'credential': { 'credential': {'write_only': True,},
'write_only': True, 'limit': {'write_only': True,},
}, 'job_tags': {'write_only': True,},
'skip_tags': {'write_only': True,},
'job_type': {'write_only': True,},
'inventory': {'write_only': True,}
} }
def get_credential_needed_to_start(self, obj): def get_credential_needed_to_start(self, obj):
return not (obj and obj.credential) return not (obj and obj.credential)
def get_inventory_needed_to_start(self, obj):
return not (obj and obj.inventory)
def get_survey_enabled(self, obj): def get_survey_enabled(self, obj):
if obj: if obj:
return obj.survey_enabled and 'spec' in obj.survey_spec return obj.survey_enabled and 'spec' in obj.survey_spec
@@ -2126,7 +2146,10 @@ class JobLaunchSerializer(BaseSerializer):
obj = self.context.get('obj') obj = self.context.get('obj')
data = self.context.get('data') data = self.context.get('data')
credential = attrs.get('credential', obj and obj.credential or None) if (not obj.ask_credential_on_launch) or (not attrs.get('credential', None)):
credential = obj.credential
else:
credential = attrs.get('credential', None)
if not credential: if not credential:
errors['credential'] = 'Credential not provided' errors['credential'] = 'Credential not provided'
@@ -2160,13 +2183,27 @@ class JobLaunchSerializer(BaseSerializer):
if obj.job_type != PERM_INVENTORY_SCAN and (obj.project is None): if obj.job_type != PERM_INVENTORY_SCAN and (obj.project is None):
errors['project'] = 'Job Template Project is missing or undefined' errors['project'] = 'Job Template Project is missing or undefined'
if obj.inventory is None: if (obj.inventory is None) and not attrs.get('inventory', None):
errors['inventory'] = 'Job Template Inventory is missing or undefined' errors['inventory'] = 'Job Template Inventory is missing or undefined'
if errors: if errors:
raise serializers.ValidationError(errors) raise serializers.ValidationError(errors)
JT_extra_vars = obj.extra_vars
JT_limit = obj.limit
JT_job_type = obj.job_type
JT_job_tags = obj.job_tags
JT_skip_tags = obj.skip_tags
JT_inventory = obj.inventory
JT_credential = obj.credential
attrs = super(JobLaunchSerializer, self).validate(attrs) attrs = super(JobLaunchSerializer, self).validate(attrs)
obj.extra_vars = JT_extra_vars
obj.limit = JT_limit
obj.job_type = JT_job_type
obj.skip_tags = JT_skip_tags
obj.job_tags = JT_job_tags
obj.inventory = JT_inventory
obj.credential = JT_credential
return attrs return attrs
class NotifierSerializer(BaseSerializer): class NotifierSerializer(BaseSerializer):

View File

@@ -6,24 +6,40 @@ The response will include the following fields:
* `ask_variables_on_launch`: Flag indicating whether the job_template is * `ask_variables_on_launch`: Flag indicating whether the job_template is
configured to prompt for variables upon launch (boolean, read-only) configured to prompt for variables upon launch (boolean, read-only)
* `ask_tags_on_launch`: Flag indicating whether the job_template is
configured to prompt for tags upon launch (boolean, read-only)
* `ask_job_type_on_launch`: Flag indicating whether the job_template is
configured to prompt for job_type upon launch (boolean, read-only)
* `ask_limit_on_launch`: Flag indicating whether the job_template is
configured to prompt for limit upon launch (boolean, read-only)
* `ask_inventory_on_launch`: Flag indicating whether the job_template is
configured to prompt for inventory upon launch (boolean, read-only)
* `ask_credential_on_launch`: Flag indicating whether the job_template is
configured to prompt for credential upon launch (boolean, read-only)
* `can_start_without_user_input`: Flag indicating if the job_template can be * `can_start_without_user_input`: Flag indicating if the job_template can be
launched without user-input (boolean, read-only) launched without user-input (boolean, read-only)
* `passwords_needed_to_start`: Password names required to launch the * `passwords_needed_to_start`: Password names required to launch the
job_template (array, read-only) job_template (array, read-only)
* `variables_needed_to_start`: Required variable names required to launch the * `variables_needed_to_start`: Required variable names required to launch the
job_template (array, read-only) job_template (array, read-only)
* `survey_enabled`: Flag indicating if whether the job_template has an enabled * `survey_enabled`: Flag indicating whether the job_template has an enabled
survey (boolean, read-only) survey (boolean, read-only)
* `credential_needed_to_start`: Flag indicating the presence of a credential * `credential_needed_to_start`: Flag indicating the presence of a credential
associated with the job template. If not then one should be supplied when associated with the job template. If not then one should be supplied when
launching the job (boolean, read-only) launching the job (boolean, read-only)
* `inventory_needed_to_start`: Flag indicating the presence of an inventory
associated with the job template. If not then one should be supplied when
launching the job (boolean, read-only)
Make a POST request to this resource to launch the job_template. If any Make a POST request to this resource to launch the job_template. If any
passwords or extra variables (extra_vars) are required, they must be passed passwords, inventory, or extra variables (extra_vars) are required, they must
via POST data, with extra_vars given as a YAML or JSON string and escaped be passed via POST data, with extra_vars given as a YAML or JSON string and
parentheses. If `credential_needed_to_start` is `True` then the `credential` escaped parentheses. If `credential_needed_to_start` is `True` then the
field is required as well. `credential` field is required and if the `inventory_needed_to_start` is
`True` then the `inventory` is required as well.
If successful, the response status code will be 202. If any required passwords If successful, the response status code will be 201. If any required passwords
are not provided, a 400 status code will be returned. If the job cannot be are not provided, a 400 status code will be returned. If the job cannot be
launched, a 405 status code will be returned. launched, a 405 status code will be returned. If the provided credential or
inventory are not allowed to be used by the user, then a 403 status code will
be returned.

View File

@@ -2082,17 +2082,23 @@ class JobTemplateLaunch(RetrieveAPIView, GenericAPIView):
def update_raw_data(self, data): def update_raw_data(self, data):
obj = self.get_object() obj = self.get_object()
extra_vars = data.get('extra_vars') or {} extra_vars = data.pop('extra_vars', None) or {}
if obj: if obj:
for p in obj.passwords_needed_to_start: for p in obj.passwords_needed_to_start:
data[p] = u'' data[p] = u''
if obj.credential:
data.pop('credential', None)
else:
data['credential'] = None
for v in obj.variables_needed_to_start: for v in obj.variables_needed_to_start:
extra_vars.setdefault(v, u'') extra_vars.setdefault(v, u'')
data['extra_vars'] = extra_vars if extra_vars:
data['extra_vars'] = extra_vars
ask_for_vars_dict = obj._ask_for_vars_dict()
ask_for_vars_dict.pop('extra_vars')
for field in ask_for_vars_dict:
if not ask_for_vars_dict[field]:
data.pop(field, None)
elif field == 'inventory' or field == 'credential':
data[field] = getattrd(obj, "%s.%s" % (field, 'id'), None)
else:
data[field] = getattr(obj, field)
return data return data
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
@@ -2102,21 +2108,27 @@ class JobTemplateLaunch(RetrieveAPIView, GenericAPIView):
if 'credential' not in request.data and 'credential_id' in request.data: if 'credential' not in request.data and 'credential_id' in request.data:
request.data['credential'] = request.data['credential_id'] request.data['credential'] = request.data['credential_id']
if 'inventory' not in request.data and 'inventory_id' in request.data:
request.data['inventory'] = request.data['inventory_id']
passwords = {} passwords = {}
serializer = self.serializer_class(instance=obj, data=request.data, context={'obj': obj, 'data': request.data, 'passwords': passwords}) serializer = self.serializer_class(instance=obj, data=request.data, context={'obj': obj, 'data': request.data, 'passwords': passwords})
if not serializer.is_valid(): if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# At this point, a credential is gauranteed to exist at serializer.instance.credential prompted_fields, ignored_fields = obj._accept_or_ignore_job_kwargs(**request.data)
if not request.user.can_access(Credential, 'read', serializer.instance.credential):
raise PermissionDenied()
kv = { if 'credential' in prompted_fields and prompted_fields['credential'] != getattrd(obj, 'credential.pk', None):
'credential': serializer.instance.credential.pk, new_credential = Credential.objects.get(pk=prompted_fields['credential'])
} if request.user not in new_credential.use_role:
if 'extra_vars' in request.data: raise PermissionDenied()
kv['extra_vars'] = request.data['extra_vars']
if 'inventory' in prompted_fields and prompted_fields['inventory'] != getattrd(obj, 'inventory.pk', None):
new_inventory = Inventory.objects.get(pk=prompted_fields['inventory'])
if request.user not in new_inventory.use_role:
raise PermissionDenied()
kv = prompted_fields
kv.update(passwords) kv.update(passwords)
new_job = obj.create_unified_job(**kv) new_job = obj.create_unified_job(**kv)
@@ -2126,8 +2138,11 @@ class JobTemplateLaunch(RetrieveAPIView, GenericAPIView):
new_job.delete() new_job.delete()
return Response(data, status=status.HTTP_400_BAD_REQUEST) return Response(data, status=status.HTTP_400_BAD_REQUEST)
else: else:
data = dict(job=new_job.id) data = OrderedDict()
return Response(data, status=status.HTTP_202_ACCEPTED) data['ignored_fields'] = ignored_fields
data.update(JobSerializer(new_job).to_representation(new_job))
data['job'] = new_job.id
return Response(data, status=status.HTTP_201_CREATED)
class JobTemplateSchedulesList(SubListCreateAttachDetachAPIView): class JobTemplateSchedulesList(SubListCreateAttachDetachAPIView):
@@ -2411,7 +2426,7 @@ class JobTemplateCallback(GenericAPIView):
# Return the location of the new job. # Return the location of the new job.
headers = {'Location': job.get_absolute_url()} headers = {'Location': job.get_absolute_url()}
return Response(status=status.HTTP_202_ACCEPTED, headers=headers) return Response(status=status.HTTP_201_CREATED, headers=headers)
class JobTemplateJobsList(SubListCreateAPIView): class JobTemplateJobsList(SubListCreateAPIView):
@@ -2459,7 +2474,7 @@ class SystemJobTemplateLaunch(GenericAPIView):
new_job = obj.create_unified_job(**request.data) new_job = obj.create_unified_job(**request.data)
new_job.signal_start(**request.data) new_job.signal_start(**request.data)
data = dict(system_job=new_job.id) data = dict(system_job=new_job.id)
return Response(data, status=status.HTTP_202_ACCEPTED) return Response(data, status=status.HTTP_201_CREATED)
class SystemJobTemplateSchedulesList(SubListCreateAttachDetachAPIView): class SystemJobTemplateSchedulesList(SubListCreateAttachDetachAPIView):

View File

@@ -1,27 +0,0 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved
from django.core.management.base import BaseCommand
from crum import impersonate
from awx.main.models import User, Organization
class Command(BaseCommand):
"""Creates the default organization if and only if no organizations
exist in the system.
"""
help = 'Creates a default organization iff there are none.'
def handle(self, *args, **kwargs):
# Sanity check: Is there already an organization in the system?
if Organization.objects.count():
return
# Create a default organization as the first superuser found.
try:
superuser = User.objects.filter(is_superuser=True).order_by('pk')[0]
except IndexError:
superuser = None
with impersonate(superuser):
Organization.objects.create(name='Default')
print('Default organization added.')

View File

@@ -0,0 +1,47 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved
from django.core.management.base import BaseCommand
from crum import impersonate
from awx.main.models import User, Organization, Project, Inventory, Credential, Host, JobTemplate
class Command(BaseCommand):
"""Create preloaded data, intended for new installs
"""
help = 'Creates a preload tower data iff there is none.'
def handle(self, *args, **kwargs):
# Sanity check: Is there already an organization in the system?
if Organization.objects.count():
return
# Create a default organization as the first superuser found.
try:
superuser = User.objects.filter(is_superuser=True).order_by('pk')[0]
except IndexError:
superuser = None
with impersonate(superuser):
o = Organization.objects.create(name='Default')
p = Project.objects.create(name='Demo Project',
scm_type='git',
scm_url='https://github.com/ansible/ansible-tower-samples',
scm_update_on_launch=True,
scm_update_cache_timeout=0,
organization=o)
c = Credential.objects.create(name='Demo Credential',
username=superuser.username,
created_by=superuser)
c.owner_role.members.add(superuser)
i = Inventory.objects.create(name='Demo Inventory',
organization=o,
created_by=superuser)
Host.objects.create(name='localhost',
inventory=i,
variables="ansible_connection: local",
created_by=superuser)
JobTemplate.objects.create(name='Demo Job Template',
project=p,
inventory=i,
credential=c)
print('Default organization added.')

View File

@@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('main', '0015_v300_label_changes'),
]
operations = [
migrations.AddField(
model_name='jobtemplate',
name='ask_limit_on_launch',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='jobtemplate',
name='ask_inventory_on_launch',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='jobtemplate',
name='ask_credential_on_launch',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='jobtemplate',
name='ask_job_type_on_launch',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='jobtemplate',
name='ask_tags_on_launch',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='job',
name='inventory',
field=models.ForeignKey(related_name='jobs', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.Inventory', null=True),
),
migrations.AlterField(
model_name='jobtemplate',
name='inventory',
field=models.ForeignKey(related_name='jobtemplates', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.Inventory', null=True),
),
]

View File

@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from awx.main.migrations import _ask_for_variables as ask_for_variables
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('main', '0016_v300_prompting_changes'),
]
operations = [
migrations.RunPython(ask_for_variables.migrate_credential),
]

View File

@@ -0,0 +1,9 @@
def migrate_credential(apps, schema_editor):
'''If credential is not currently present, set ask_for_credential_on_launch
equal to True, and otherwise leave it as the default False value.
'''
JobTemplate = apps.get_model('main', 'JobTemplate')
for jt in JobTemplate.objects.iterator():
if jt.credential is None:
jt.ask_credential_on_launch = True
jt.save()

View File

@@ -53,7 +53,9 @@ class JobOptions(BaseModel):
inventory = models.ForeignKey( inventory = models.ForeignKey(
'Inventory', 'Inventory',
related_name='%(class)ss', related_name='%(class)ss',
blank=True,
null=True, null=True,
default=None,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
) )
project = models.ForeignKey( project = models.ForeignKey(
@@ -194,6 +196,26 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, ResourceMixin):
blank=True, blank=True,
default=False, default=False,
) )
ask_limit_on_launch = models.BooleanField(
blank=True,
default=False,
)
ask_tags_on_launch = models.BooleanField(
blank=True,
default=False,
)
ask_job_type_on_launch = models.BooleanField(
blank=True,
default=False,
)
ask_inventory_on_launch = models.BooleanField(
blank=True,
default=False,
)
ask_credential_on_launch = models.BooleanField(
blank=True,
default=False,
)
survey_enabled = models.BooleanField( survey_enabled = models.BooleanField(
default=False, default=False,
@@ -236,6 +258,15 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, ResourceMixin):
'force_handlers', 'skip_tags', 'start_at_task', 'become_enabled', 'force_handlers', 'skip_tags', 'start_at_task', 'become_enabled',
'labels',] 'labels',]
def clean(self):
if self.job_type == 'scan' and (self.inventory is None or self.ask_inventory_on_launch):
raise ValidationError('Scan jobs must be assigned a fixed inventory')
if (not self.ask_inventory_on_launch) and self.inventory is None:
raise ValidationError('Job Template must either have an inventory or allow prompting for inventory')
if (not self.ask_credential_on_launch) and self.credential is None:
raise ValidationError('Job Template must either have a credential or allow prompting for credential')
return super(JobTemplate, self).clean()
def create_job(self, **kwargs): def create_job(self, **kwargs):
''' '''
Create a new job based on this template. Create a new job based on this template.
@@ -250,7 +281,9 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, ResourceMixin):
Return whether job template can be used to start a new job without Return whether job template can be used to start a new job without
requiring any user input. requiring any user input.
''' '''
return bool(self.credential and not len(self.passwords_needed_to_start) and not len(self.variables_needed_to_start)) return bool(self.credential and not len(self.passwords_needed_to_start) and
not len(self.variables_needed_to_start) and
self.inventory)
@property @property
def variables_needed_to_start(self): def variables_needed_to_start(self):
@@ -365,6 +398,50 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, ResourceMixin):
kwargs['extra_vars'] = json.dumps(extra_vars) kwargs['extra_vars'] = json.dumps(extra_vars)
return kwargs return kwargs
def _ask_for_vars_dict(self):
return dict(
extra_vars=self.ask_variables_on_launch,
limit=self.ask_limit_on_launch,
job_tags=self.ask_tags_on_launch,
skip_tags=self.ask_tags_on_launch,
job_type=self.ask_job_type_on_launch,
inventory=self.ask_inventory_on_launch,
credential=self.ask_credential_on_launch
)
def _accept_or_ignore_job_kwargs(self, **kwargs):
# Sort the runtime fields allowed and disallowed by job template
ignored_fields = {}
prompted_fields = {}
ask_for_vars_dict = self._ask_for_vars_dict()
for field in ask_for_vars_dict:
if field in kwargs:
if field == 'extra_vars':
prompted_fields[field] = {}
ignored_fields[field] = {}
if ask_for_vars_dict[field]:
prompted_fields[field] = kwargs[field]
else:
if field == 'extra_vars' and self.survey_enabled:
# Accept vars defined in the survey and no others
survey_vars = [question['variable'] for question in self.survey_spec['spec']]
for key in kwargs[field]:
if key in survey_vars:
prompted_fields[field][key] = kwargs[field][key]
else:
ignored_fields[field][key] = kwargs[field][key]
else:
ignored_fields[field] = kwargs[field]
# Special case to ignore inventory if it is a scan job
if prompted_fields.get('job_type', None) == 'scan' or self.job_type == 'scan':
if 'inventory' in prompted_fields:
ignored_fields['inventory'] = prompted_fields.pop('inventory')
return prompted_fields, ignored_fields
@property @property
def cache_timeout_blocked(self): def cache_timeout_blocked(self):
if Job.objects.filter(job_template=self, status__in=['pending', 'waiting', 'running']).count() > getattr(tower_settings, 'SCHEDULE_MAX_JOBS', 10): if Job.objects.filter(job_template=self, status__in=['pending', 'waiting', 'running']).count() > getattr(tower_settings, 'SCHEDULE_MAX_JOBS', 10):

View File

@@ -352,6 +352,7 @@ class BaseTestMixin(QueueTestMixin, MockCommonlySlowTestMixin):
'host_config_key': settings.SYSTEM_UUID, 'host_config_key': settings.SYSTEM_UUID,
'created_by': created_by, 'created_by': created_by,
'playbook': playbook, 'playbook': playbook,
'ask_credential_on_launch': True,
} }
opts.update(kwargs) opts.update(kwargs)
return JobTemplate.objects.create(**opts) return JobTemplate.objects.create(**opts)

View File

@@ -0,0 +1,295 @@
import pytest
import yaml
from awx.api.serializers import JobLaunchSerializer
from awx.main.models.credential import Credential
from awx.main.models.inventory import Inventory
from awx.main.models.jobs import Job, JobTemplate
from django.core.urlresolvers import reverse
from copy import copy
@pytest.fixture
def runtime_data(organization):
cred_obj = Credential.objects.create(name='runtime-cred', kind='ssh', username='test_user2', password='pas4word2')
inv_obj = organization.inventories.create(name="runtime-inv")
return dict(
extra_vars='{"job_launch_var": 4}',
limit='test-servers',
job_type='check',
job_tags='provision',
skip_tags='restart',
inventory=inv_obj.pk,
credential=cred_obj.pk,
)
@pytest.fixture
def job_template_prompts(project, inventory, machine_credential):
def rf(on_off):
return JobTemplate.objects.create(
job_type='run',
project=project,
inventory=inventory,
credential=machine_credential,
name='deploy-job-template',
ask_variables_on_launch=on_off,
ask_tags_on_launch=on_off,
ask_job_type_on_launch=on_off,
ask_inventory_on_launch=on_off,
ask_limit_on_launch=on_off,
ask_credential_on_launch=on_off,
)
return rf
@pytest.fixture
def job_template_prompts_null(project):
return JobTemplate.objects.create(
job_type='run',
project=project,
inventory=None,
credential=None,
name='deploy-job-template',
ask_variables_on_launch=True,
ask_tags_on_launch=True,
ask_job_type_on_launch=True,
ask_inventory_on_launch=True,
ask_limit_on_launch=True,
ask_credential_on_launch=True,
)
@pytest.mark.django_db
@pytest.mark.job_runtime_vars
def test_job_ignore_unprompted_vars(runtime_data, job_template_prompts, post, user):
job_template = job_template_prompts(False)
job_template_saved = copy(job_template)
response = post(reverse('api:job_template_launch', args=[job_template.pk]),
runtime_data, user('admin', True))
assert response.status_code == 201
job_id = response.data['job']
job_obj = Job.objects.get(pk=job_id)
# Check that job data matches job_template data
assert len(yaml.load(job_obj.extra_vars)) == 0
assert job_obj.limit == job_template_saved.limit
assert job_obj.job_type == job_template_saved.job_type
assert job_obj.inventory.pk == job_template_saved.inventory.pk
assert job_obj.job_tags == job_template_saved.job_tags
assert job_obj.credential.pk == job_template_saved.credential.pk
# Check that response tells us what things were ignored
assert 'job_launch_var' in response.data['ignored_fields']['extra_vars']
assert 'job_type' in response.data['ignored_fields']
assert 'limit' in response.data['ignored_fields']
assert 'inventory' in response.data['ignored_fields']
assert 'credential' in response.data['ignored_fields']
assert 'job_tags' in response.data['ignored_fields']
assert 'skip_tags' in response.data['ignored_fields']
@pytest.mark.django_db
@pytest.mark.job_runtime_vars
def test_job_accept_prompted_vars(runtime_data, job_template_prompts, post, user):
job_template = job_template_prompts(True)
admin_user = user('admin', True)
job_template.inventory.execute_role.members.add(admin_user)
response = post(reverse('api:job_template_launch', args=[job_template.pk]),
runtime_data, admin_user)
assert response.status_code == 201
job_id = response.data['job']
job_obj = Job.objects.get(pk=job_id)
# Check that job data matches the given runtime variables
assert 'job_launch_var' in yaml.load(job_obj.extra_vars)
assert job_obj.limit == runtime_data['limit']
assert job_obj.job_type == runtime_data['job_type']
assert job_obj.inventory.pk == runtime_data['inventory']
assert job_obj.credential.pk == runtime_data['credential']
assert job_obj.job_tags == runtime_data['job_tags']
@pytest.mark.django_db
@pytest.mark.job_runtime_vars
def test_job_accept_prompted_vars_null(runtime_data, job_template_prompts_null, post, user):
job_template = job_template_prompts_null
common_user = user('not-admin', False)
# Give user permission to execute the job template
job_template.execute_role.members.add(common_user)
# Give user permission to use inventory and credential at runtime
credential = Credential.objects.get(pk=runtime_data['credential'])
credential.use_role.members.add(common_user)
inventory = Inventory.objects.get(pk=runtime_data['inventory'])
inventory.use_role.members.add(common_user)
response = post(reverse('api:job_template_launch', args=[job_template.pk]),
runtime_data, common_user)
assert response.status_code == 201
job_id = response.data['job']
job_obj = Job.objects.get(pk=job_id)
# Check that job data matches the given runtime variables
assert 'job_launch_var' in yaml.load(job_obj.extra_vars)
assert job_obj.limit == runtime_data['limit']
assert job_obj.job_type == runtime_data['job_type']
assert job_obj.inventory.pk == runtime_data['inventory']
assert job_obj.credential.pk == runtime_data['credential']
assert job_obj.job_tags == runtime_data['job_tags']
@pytest.mark.django_db
@pytest.mark.job_runtime_vars
def test_job_reject_invalid_prompted_vars(runtime_data, job_template_prompts, post, user):
job_template = job_template_prompts(True)
response = post(
reverse('api:job_template_launch', args=[job_template.pk]),
dict(job_type='foobicate', # foobicate is not a valid job type
inventory=87865, credential=48474), user('admin', True))
assert response.status_code == 400
assert response.data['job_type'] == [u'"foobicate" is not a valid choice.']
assert response.data['inventory'] == [u'Invalid pk "87865" - object does not exist.']
assert response.data['credential'] == [u'Invalid pk "48474" - object does not exist.']
@pytest.mark.django_db
@pytest.mark.job_runtime_vars
def test_job_reject_invalid_prompted_extra_vars(runtime_data, job_template_prompts, post, user):
job_template = job_template_prompts(True)
response = post(
reverse('api:job_template_launch', args=[job_template.pk]),
dict(extra_vars='{"unbalanced brackets":'), user('admin', True))
assert response.status_code == 400
assert response.data['extra_vars'] == ['Must be valid JSON or YAML']
@pytest.mark.django_db
@pytest.mark.job_runtime_vars
def test_job_launch_fails_without_inventory(deploy_jobtemplate, post, user):
deploy_jobtemplate.inventory = None
deploy_jobtemplate.save()
response = post(reverse('api:job_template_launch',
args=[deploy_jobtemplate.pk]), {}, user('admin', True))
assert response.status_code == 400
assert response.data['inventory'] == ['Job Template Inventory is missing or undefined']
@pytest.mark.django_db
@pytest.mark.job_runtime_vars
def test_job_launch_fails_without_inventory_access(deploy_jobtemplate, machine_credential, post, user):
deploy_jobtemplate.ask_inventory_on_launch = True
deploy_jobtemplate.credential = machine_credential
deploy_jobtemplate.save()
common_user = user('test-user', False)
deploy_jobtemplate.execute_role.members.add(common_user)
deploy_jobtemplate.inventory.use_role.members.add(common_user)
deploy_jobtemplate.project.member_role.members.add(common_user)
deploy_jobtemplate.credential.use_role.members.add(common_user)
# Assure that the base job template can be launched to begin with
response = post(reverse('api:job_template_launch',
args=[deploy_jobtemplate.pk]), {}, common_user)
assert response.status_code == 201
# Assure that giving an inventory without access to the inventory blocks the launch
new_inv = deploy_jobtemplate.project.organization.inventories.create(name="user-can-not-use")
response = post(reverse('api:job_template_launch', args=[deploy_jobtemplate.pk]),
dict(inventory=new_inv.pk), common_user)
assert response.status_code == 403
assert response.data['detail'] == u'You do not have permission to perform this action.'
@pytest.mark.django_db
@pytest.mark.job_runtime_vars
def test_job_relaunch_prompted_vars(runtime_data, job_template_prompts, post, user):
job_template = job_template_prompts(True)
admin_user = user('admin', True)
# Launch job, overwriting several JT fields
first_response = post(reverse('api:job_template_launch', args=[job_template.pk]),
runtime_data, admin_user)
assert first_response.status_code == 201
original_job = Job.objects.get(pk=first_response.data['job'])
# Launch a second job as a relaunch of the first
second_response = post(reverse('api:job_relaunch', args=[original_job.pk]),
{}, admin_user)
relaunched_job = Job.objects.get(pk=second_response.data['job'])
# Check that job data matches the original runtime variables
assert first_response.status_code == 201
assert 'job_launch_var' in yaml.load(relaunched_job.extra_vars)
assert relaunched_job.limit == runtime_data['limit']
assert relaunched_job.job_type == runtime_data['job_type']
assert relaunched_job.inventory.pk == runtime_data['inventory']
assert relaunched_job.job_tags == runtime_data['job_tags']
@pytest.mark.django_db
def test_job_launch_JT_with_validation(machine_credential, deploy_jobtemplate):
deploy_jobtemplate.extra_vars = '{"job_template_var": 3}'
deploy_jobtemplate.ask_credential_on_launch = True
deploy_jobtemplate.save()
kv = dict(extra_vars={"job_launch_var": 4}, credential=machine_credential.id)
serializer = JobLaunchSerializer(
instance=deploy_jobtemplate, data=kv,
context={'obj': deploy_jobtemplate, 'data': kv, 'passwords': {}})
validated = serializer.is_valid()
assert validated
job_obj = deploy_jobtemplate.create_unified_job(**kv)
result = job_obj.signal_start(**kv)
final_job_extra_vars = yaml.load(job_obj.extra_vars)
assert result
assert 'job_template_var' in final_job_extra_vars
assert 'job_launch_var' in final_job_extra_vars
assert job_obj.credential.id == machine_credential.id
@pytest.mark.django_db
@pytest.mark.job_runtime_vars
def test_job_launch_unprompted_vars_with_survey(mocker, job_template_prompts, post, user):
with mocker.patch('awx.main.access.BaseAccess.check_license', return_value=False):
job_template = job_template_prompts(False)
job_template.survey_enabled = True
job_template.survey_spec = {
"spec": [
{
"index": 0,
"question_name": "survey_var",
"min": 0,
"default": "",
"max": 100,
"question_description": "A survey question",
"required": True,
"variable": "survey_var",
"choices": "",
"type": "integer"
}
],
"description": "",
"name": ""
}
job_template.save()
response = post(
reverse('api:job_template_launch', args=[job_template.pk]),
dict(extra_vars={"job_launch_var": 3, "survey_var": 4}),
user('admin', True))
assert response.status_code == 201
job_id = response.data['job']
job_obj = Job.objects.get(pk=job_id)
# Check that the survey variable is accepted and the job variable isn't
job_extra_vars = yaml.load(job_obj.extra_vars)
assert 'job_launch_var' not in job_extra_vars
assert 'survey_var' in job_extra_vars

View File

@@ -167,6 +167,10 @@ def organization_factory(instance):
def credential(): def credential():
return Credential.objects.create(kind='aws', name='test-cred') return Credential.objects.create(kind='aws', name='test-cred')
@pytest.fixture
def machine_credential():
return Credential.objects.create(name='machine-cred', kind='ssh', username='test_user', password='pas4word')
@pytest.fixture @pytest.fixture
def inventory(organization): def inventory(organization):
return organization.inventories.create(name="test-inv") return organization.inventories.create(name="test-inv")

View File

@@ -503,6 +503,7 @@ class BaseJobTestMixin(BaseTestMixin):
playbook=self.proj_dev.playbooks[0], playbook=self.proj_dev.playbooks[0],
host_config_key=uuid.uuid4().hex, host_config_key=uuid.uuid4().hex,
created_by=self.user_sue, created_by=self.user_sue,
ask_credential_on_launch=True,
) )
# self.job_eng_run = self.jt_eng_run.create_job( # self.job_eng_run = self.jt_eng_run.create_job(
# created_by=self.user_sue, # created_by=self.user_sue,

View File

@@ -191,32 +191,23 @@ class CreateDefaultOrgTest(BaseCommandMixin, BaseTest):
def setUp(self): def setUp(self):
super(CreateDefaultOrgTest, self).setUp() super(CreateDefaultOrgTest, self).setUp()
self.setup_instances()
def test_create_default_org(self): def test_create_default_org(self):
self.setup_users() self.setup_users()
self.assertEqual(Organization.objects.count(), 0) self.assertEqual(Organization.objects.count(), 0)
result, stdout, stderr = self.run_command('create_default_org') result, stdout, stderr = self.run_command('create_preload_data')
self.assertEqual(result, None) self.assertEqual(result, None)
self.assertTrue('Default organization added' in stdout) self.assertTrue('Default organization added' in stdout)
self.assertEqual(Organization.objects.count(), 1) self.assertEqual(Organization.objects.count(), 1)
org = Organization.objects.all()[0] org = Organization.objects.all()[0]
self.assertEqual(org.created_by, self.super_django_user) self.assertEqual(org.created_by, self.super_django_user)
self.assertEqual(org.modified_by, self.super_django_user) self.assertEqual(org.modified_by, self.super_django_user)
result, stdout, stderr = self.run_command('create_default_org') result, stdout, stderr = self.run_command('create_preload_data')
self.assertEqual(result, None) self.assertEqual(result, None)
self.assertFalse('Default organization added' in stdout) self.assertFalse('Default organization added' in stdout)
self.assertEqual(Organization.objects.count(), 1) self.assertEqual(Organization.objects.count(), 1)
def test_create_default_org_when_no_superuser_exists(self):
self.assertEqual(Organization.objects.count(), 0)
result, stdout, stderr = self.run_command('create_default_org')
self.assertEqual(result, None)
self.assertTrue('Default organization added' in stdout)
self.assertEqual(Organization.objects.count(), 1)
org = Organization.objects.all()[0]
self.assertEqual(org.created_by, None)
self.assertEqual(org.modified_by, None)
class DumpDataTest(BaseCommandMixin, BaseTest): class DumpDataTest(BaseCommandMixin, BaseTest):
''' '''
Test cases for dumpdata management command. Test cases for dumpdata management command.

View File

@@ -27,6 +27,8 @@ class JobTemplateLaunchTest(BaseJobTestMixin, django.test.TransactionTestCase):
project = self.proj_dev.pk, project = self.proj_dev.pk,
credential = self.cred_sue.pk, credential = self.cred_sue.pk,
playbook = self.proj_dev.playbooks[0], playbook = self.proj_dev.playbooks[0],
ask_variables_on_launch = True,
ask_credential_on_launch = True,
) )
self.data_no_cred = dict( self.data_no_cred = dict(
name = 'launched job template no credential', name = 'launched job template no credential',
@@ -34,6 +36,8 @@ class JobTemplateLaunchTest(BaseJobTestMixin, django.test.TransactionTestCase):
inventory = self.inv_eng.pk, inventory = self.inv_eng.pk,
project = self.proj_dev.pk, project = self.proj_dev.pk,
playbook = self.proj_dev.playbooks[0], playbook = self.proj_dev.playbooks[0],
ask_credential_on_launch = True,
ask_variables_on_launch = True,
) )
self.data_cred_ask = dict(self.data) self.data_cred_ask = dict(self.data)
self.data_cred_ask['name'] = 'launched job templated with ask passwords' self.data_cred_ask['name'] = 'launched job templated with ask passwords'
@@ -67,7 +71,7 @@ class JobTemplateLaunchTest(BaseJobTestMixin, django.test.TransactionTestCase):
def test_credential_implicit(self): def test_credential_implicit(self):
# Implicit, attached credentials # Implicit, attached credentials
with self.current_user(self.user_sue): with self.current_user(self.user_sue):
response = self.post(self.launch_url, {}, expect=202) response = self.post(self.launch_url, {}, expect=201)
j = Job.objects.get(pk=response['job']) j = Job.objects.get(pk=response['job'])
self.assertTrue(j.status == 'new') self.assertTrue(j.status == 'new')
@@ -75,7 +79,7 @@ class JobTemplateLaunchTest(BaseJobTestMixin, django.test.TransactionTestCase):
# Sending extra_vars as a JSON string, implicit credentials # Sending extra_vars as a JSON string, implicit credentials
with self.current_user(self.user_sue): with self.current_user(self.user_sue):
data = dict(extra_vars = '{\"a\":3}') data = dict(extra_vars = '{\"a\":3}')
response = self.post(self.launch_url, data, expect=202) response = self.post(self.launch_url, data, expect=201)
j = Job.objects.get(pk=response['job']) j = Job.objects.get(pk=response['job'])
ev_dict = yaml.load(j.extra_vars) ev_dict = yaml.load(j.extra_vars)
self.assertIn('a', ev_dict) self.assertIn('a', ev_dict)
@@ -86,7 +90,7 @@ class JobTemplateLaunchTest(BaseJobTestMixin, django.test.TransactionTestCase):
# Sending extra_vars as a JSON string, implicit credentials # Sending extra_vars as a JSON string, implicit credentials
with self.current_user(self.user_sue): with self.current_user(self.user_sue):
data = dict(extra_vars = 'a: 3') data = dict(extra_vars = 'a: 3')
response = self.post(self.launch_url, data, expect=202) response = self.post(self.launch_url, data, expect=201)
j = Job.objects.get(pk=response['job']) j = Job.objects.get(pk=response['job'])
ev_dict = yaml.load(j.extra_vars) ev_dict = yaml.load(j.extra_vars)
self.assertIn('a', ev_dict) self.assertIn('a', ev_dict)
@@ -97,7 +101,7 @@ class JobTemplateLaunchTest(BaseJobTestMixin, django.test.TransactionTestCase):
# Explicit, credential # Explicit, credential
with self.current_user(self.user_sue): with self.current_user(self.user_sue):
self.cred_sue.delete() self.cred_sue.delete()
response = self.post(self.launch_url, {'credential': self.cred_doug.pk}, expect=202) response = self.post(self.launch_url, {'credential': self.cred_doug.pk}, expect=201)
j = Job.objects.get(pk=response['job']) j = Job.objects.get(pk=response['job'])
self.assertEqual(j.status, 'new') self.assertEqual(j.status, 'new')
self.assertEqual(j.credential.pk, self.cred_doug.pk) self.assertEqual(j.credential.pk, self.cred_doug.pk)
@@ -106,7 +110,7 @@ class JobTemplateLaunchTest(BaseJobTestMixin, django.test.TransactionTestCase):
# Explicit, credential # Explicit, credential
with self.current_user(self.user_sue): with self.current_user(self.user_sue):
self.cred_sue.delete() self.cred_sue.delete()
response = self.post(self.launch_url, {'credential_id': self.cred_doug.pk}, expect=202) response = self.post(self.launch_url, {'credential_id': self.cred_doug.pk}, expect=201)
j = Job.objects.get(pk=response['job']) j = Job.objects.get(pk=response['job'])
self.assertEqual(j.status, 'new') self.assertEqual(j.status, 'new')
self.assertEqual(j.credential.pk, self.cred_doug.pk) self.assertEqual(j.credential.pk, self.cred_doug.pk)
@@ -114,7 +118,7 @@ class JobTemplateLaunchTest(BaseJobTestMixin, django.test.TransactionTestCase):
def test_credential_override(self): def test_credential_override(self):
# Explicit, credential # Explicit, credential
with self.current_user(self.user_sue): with self.current_user(self.user_sue):
response = self.post(self.launch_url, {'credential': self.cred_doug.pk}, expect=202) response = self.post(self.launch_url, {'credential': self.cred_doug.pk}, expect=201)
j = Job.objects.get(pk=response['job']) j = Job.objects.get(pk=response['job'])
self.assertEqual(j.status, 'new') self.assertEqual(j.status, 'new')
self.assertEqual(j.credential.pk, self.cred_doug.pk) self.assertEqual(j.credential.pk, self.cred_doug.pk)
@@ -122,7 +126,7 @@ class JobTemplateLaunchTest(BaseJobTestMixin, django.test.TransactionTestCase):
def test_credential_override_via_credential_id(self): def test_credential_override_via_credential_id(self):
# Explicit, credential # Explicit, credential
with self.current_user(self.user_sue): with self.current_user(self.user_sue):
response = self.post(self.launch_url, {'credential_id': self.cred_doug.pk}, expect=202) response = self.post(self.launch_url, {'credential_id': self.cred_doug.pk}, expect=201)
j = Job.objects.get(pk=response['job']) j = Job.objects.get(pk=response['job'])
self.assertEqual(j.status, 'new') self.assertEqual(j.status, 'new')
self.assertEqual(j.credential.pk, self.cred_doug.pk) self.assertEqual(j.credential.pk, self.cred_doug.pk)
@@ -191,6 +195,7 @@ class JobTemplateLaunchPasswordsTest(BaseJobTestMixin, django.test.TransactionTe
project = self.proj_dev.pk, project = self.proj_dev.pk,
credential = self.cred_sue_ask.pk, credential = self.cred_sue_ask.pk,
playbook = self.proj_dev.playbooks[0], playbook = self.proj_dev.playbooks[0],
ask_credential_on_launch = True,
) )
with self.current_user(self.user_sue): with self.current_user(self.user_sue):
@@ -211,7 +216,7 @@ class JobTemplateLaunchPasswordsTest(BaseJobTestMixin, django.test.TransactionTe
def test_explicit_cred_with_ask_password(self): def test_explicit_cred_with_ask_password(self):
with self.current_user(self.user_sue): with self.current_user(self.user_sue):
response = self.post(self.launch_url, {'ssh_password': 'whatever'}, expect=202) response = self.post(self.launch_url, {'ssh_password': 'whatever'}, expect=201)
j = Job.objects.get(pk=response['job']) j = Job.objects.get(pk=response['job'])
self.assertEqual(j.status, 'new') self.assertEqual(j.status, 'new')

View File

@@ -798,7 +798,7 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase):
self.assertEqual(jobs_qs.count(), 0) self.assertEqual(jobs_qs.count(), 0)
# Create the job itself. # Create the job itself.
result = self.post(url, data, expect=202, remote_addr=host_ip) result = self.post(url, data, expect=201, remote_addr=host_ip)
# Establish that we got back what we expect, and made the changes # Establish that we got back what we expect, and made the changes
# that we expect. # that we expect.
@@ -813,7 +813,7 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase):
self.assertEqual(job.hosts.all()[0], host) self.assertEqual(job.hosts.all()[0], host)
# Create the job itself using URL-encoded form data instead of JSON. # Create the job itself using URL-encoded form data instead of JSON.
result = self.post(url, data, expect=202, remote_addr=host_ip, data_type='form') result = self.post(url, data, expect=201, remote_addr=host_ip, data_type='form')
# Establish that we got back what we expect, and made the changes # Establish that we got back what we expect, and made the changes
# that we expect. # that we expect.
@@ -829,7 +829,7 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase):
# Run the callback job again with extra vars and verify their presence # Run the callback job again with extra vars and verify their presence
data.update(dict(extra_vars=dict(key="value"))) data.update(dict(extra_vars=dict(key="value")))
result = self.post(url, data, expect=202, remote_addr=host_ip) result = self.post(url, data, expect=201, remote_addr=host_ip)
jobs_qs = job_template.jobs.filter(launch_type='callback').order_by('-pk') jobs_qs = job_template.jobs.filter(launch_type='callback').order_by('-pk')
job = jobs_qs[0] job = jobs_qs[0]
self.assertTrue("key" in job.extra_vars) self.assertTrue("key" in job.extra_vars)
@@ -878,7 +878,7 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase):
break break
self.assertTrue(host) self.assertTrue(host)
self.assertEqual(jobs_qs.count(), 3) self.assertEqual(jobs_qs.count(), 3)
self.post(url, data, expect=202, remote_addr=host_ip) self.post(url, data, expect=201, remote_addr=host_ip)
self.assertEqual(jobs_qs.count(), 4) self.assertEqual(jobs_qs.count(), 4)
job = jobs_qs[0] job = jobs_qs[0]
self.assertEqual(job.launch_type, 'callback') self.assertEqual(job.launch_type, 'callback')
@@ -903,7 +903,7 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase):
break break
self.assertTrue(host) self.assertTrue(host)
self.assertEqual(jobs_qs.count(), 4) self.assertEqual(jobs_qs.count(), 4)
self.post(url, data, expect=202, remote_addr=host_ip) self.post(url, data, expect=201, remote_addr=host_ip)
self.assertEqual(jobs_qs.count(), 5) self.assertEqual(jobs_qs.count(), 5)
job = jobs_qs[0] job = jobs_qs[0]
self.assertEqual(job.launch_type, 'callback') self.assertEqual(job.launch_type, 'callback')
@@ -917,7 +917,7 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase):
host = host_qs[0] host = host_qs[0]
host_ip = host.variables_dict['ansible_ssh_host'] host_ip = host.variables_dict['ansible_ssh_host']
self.assertEqual(jobs_qs.count(), 5) self.assertEqual(jobs_qs.count(), 5)
self.post(url, data, expect=202, remote_addr=host_ip) self.post(url, data, expect=201, remote_addr=host_ip)
self.assertEqual(jobs_qs.count(), 6) self.assertEqual(jobs_qs.count(), 6)
job = jobs_qs[0] job = jobs_qs[0]
self.assertEqual(job.launch_type, 'callback') self.assertEqual(job.launch_type, 'callback')
@@ -951,7 +951,7 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase):
break break
self.assertTrue(host) self.assertTrue(host)
self.assertEqual(jobs_qs.count(), 6) self.assertEqual(jobs_qs.count(), 6)
self.post(url, data, expect=202, remote_addr=host_ip) self.post(url, data, expect=201, remote_addr=host_ip)
self.assertEqual(jobs_qs.count(), 7) self.assertEqual(jobs_qs.count(), 7)
job = jobs_qs[0] job = jobs_qs[0]
self.assertEqual(job.launch_type, 'callback') self.assertEqual(job.launch_type, 'callback')
@@ -1087,7 +1087,7 @@ class JobTransactionTest(BaseJobTestMixin, django.test.LiveServerTestCase):
response = self.get(url) response = self.get(url)
self.assertTrue(response['can_start']) self.assertTrue(response['can_start'])
self.assertFalse(response['passwords_needed_to_start']) self.assertFalse(response['passwords_needed_to_start'])
response = self.post(url, {}, expect=202) response = self.post(url, {}, expect=201)
job = Job.objects.get(pk=job.pk) job = Job.objects.get(pk=job.pk)
self.assertEqual(job.status, 'successful', job.result_stdout) self.assertEqual(job.status, 'successful', job.result_stdout)
self.assertFalse(errors) self.assertFalse(errors)
@@ -1146,14 +1146,14 @@ class JobTemplateSurveyTest(BaseJobTestMixin, django.test.TransactionTestCase):
# should return, and should be able to launch template without error. # should return, and should be able to launch template without error.
response = self.get(launch_url) response = self.get(launch_url)
self.assertFalse(response['survey_enabled']) self.assertFalse(response['survey_enabled'])
self.post(launch_url, {}, expect=202) self.post(launch_url, {}, expect=201)
# Now post a survey spec and check that the answer is set in the # Now post a survey spec and check that the answer is set in the
# job's extra vars. # job's extra vars.
self.post(url, json.loads(TEST_SIMPLE_REQUIRED_SURVEY), expect=200) self.post(url, json.loads(TEST_SIMPLE_REQUIRED_SURVEY), expect=200)
response = self.get(launch_url) response = self.get(launch_url)
self.assertTrue(response['survey_enabled']) self.assertTrue(response['survey_enabled'])
self.assertTrue('favorite_color' in response['variables_needed_to_start']) self.assertTrue('favorite_color' in response['variables_needed_to_start'])
response = self.post(launch_url, dict(extra_vars=dict(favorite_color="green")), expect=202) response = self.post(launch_url, dict(extra_vars=dict(favorite_color="green")), expect=201)
job = Job.objects.get(pk=response["job"]) job = Job.objects.get(pk=response["job"])
job_extra = json.loads(job.extra_vars) job_extra = json.loads(job.extra_vars)
self.assertTrue("favorite_color" in job_extra) self.assertTrue("favorite_color" in job_extra)
@@ -1187,7 +1187,7 @@ class JobTemplateSurveyTest(BaseJobTestMixin, django.test.TransactionTestCase):
with self.current_user(self.user_sue): with self.current_user(self.user_sue):
response = self.post(url, json.loads(TEST_SURVEY_REQUIREMENTS), expect=200) response = self.post(url, json.loads(TEST_SURVEY_REQUIREMENTS), expect=200)
# Just the required answer should work # Just the required answer should work
self.post(launch_url, dict(extra_vars=dict(reqd_answer="foo")), expect=202) self.post(launch_url, dict(extra_vars=dict(reqd_answer="foo")), expect=201)
# Short answer but requires a long answer # Short answer but requires a long answer
self.post(launch_url, dict(extra_vars=dict(long_answer='a', reqd_answer="foo")), expect=400) self.post(launch_url, dict(extra_vars=dict(long_answer='a', reqd_answer="foo")), expect=400)
# Long answer but requires a short answer # Long answer but requires a short answer
@@ -1199,9 +1199,9 @@ class JobTemplateSurveyTest(BaseJobTestMixin, django.test.TransactionTestCase):
# Integer that's too big # Integer that's too big
self.post(launch_url, dict(extra_vars=dict(int_answer=10, reqd_answer="foo")), expect=400) self.post(launch_url, dict(extra_vars=dict(int_answer=10, reqd_answer="foo")), expect=400)
# Integer that's just riiiiight # Integer that's just riiiiight
self.post(launch_url, dict(extra_vars=dict(int_answer=3, reqd_answer="foo")), expect=202) self.post(launch_url, dict(extra_vars=dict(int_answer=3, reqd_answer="foo")), expect=201)
# Integer bigger than min with no max defined # Integer bigger than min with no max defined
self.post(launch_url, dict(extra_vars=dict(int_answer_no_max=3, reqd_answer="foo")), expect=202) self.post(launch_url, dict(extra_vars=dict(int_answer_no_max=3, reqd_answer="foo")), expect=201)
# Integer answer that's the wrong type # Integer answer that's the wrong type
self.post(launch_url, dict(extra_vars=dict(int_answer="test", reqd_answer="foo")), expect=400) self.post(launch_url, dict(extra_vars=dict(int_answer="test", reqd_answer="foo")), expect=400)
# Float that's too big # Float that's too big
@@ -1209,7 +1209,7 @@ class JobTemplateSurveyTest(BaseJobTestMixin, django.test.TransactionTestCase):
# Float that's too small # Float that's too small
self.post(launch_url, dict(extra_vars=dict(float_answer=1.995, reqd_answer="foo")), expect=400) self.post(launch_url, dict(extra_vars=dict(float_answer=1.995, reqd_answer="foo")), expect=400)
# float that's just riiiiight # float that's just riiiiight
self.post(launch_url, dict(extra_vars=dict(float_answer=2.01, reqd_answer="foo")), expect=202) self.post(launch_url, dict(extra_vars=dict(float_answer=2.01, reqd_answer="foo")), expect=201)
# float answer that's the wrong type # float answer that's the wrong type
self.post(launch_url, dict(extra_vars=dict(float_answer="test", reqd_answer="foo")), expect=400) self.post(launch_url, dict(extra_vars=dict(float_answer="test", reqd_answer="foo")), expect=400)
# Wrong choice in single choice # Wrong choice in single choice
@@ -1219,11 +1219,11 @@ class JobTemplateSurveyTest(BaseJobTestMixin, django.test.TransactionTestCase):
# Wrong type for multi choicen # Wrong type for multi choicen
self.post(launch_url, dict(extra_vars=dict(reqd_answer="foo", multi_choice="two")), expect=400) self.post(launch_url, dict(extra_vars=dict(reqd_answer="foo", multi_choice="two")), expect=400)
# Right choice in single choice # Right choice in single choice
self.post(launch_url, dict(extra_vars=dict(reqd_answer="foo", single_choice="two")), expect=202) self.post(launch_url, dict(extra_vars=dict(reqd_answer="foo", single_choice="two")), expect=201)
# Right choices in multi choice # Right choices in multi choice
self.post(launch_url, dict(extra_vars=dict(reqd_answer="foo", multi_choice=["one", "two"])), expect=202) self.post(launch_url, dict(extra_vars=dict(reqd_answer="foo", multi_choice=["one", "two"])), expect=201)
# Nested json # Nested json
self.post(launch_url, dict(extra_vars=dict(json_answer=dict(test="val", num=1), reqd_answer="foo")), expect=202) self.post(launch_url, dict(extra_vars=dict(json_answer=dict(test="val", num=1), reqd_answer="foo")), expect=201)
# Bob can access and update the survey because he's an org-admin # Bob can access and update the survey because he's an org-admin
with self.current_user(self.user_bob): with self.current_user(self.user_bob):