mirror of
https://github.com/ansible/awx.git
synced 2026-03-26 13:25:02 -02:30
Merge pull request #9 from AlanCoding/11th-hour
Merge devel, update job launch behavior to newest permissions stuff
This commit is contained in:
@@ -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):
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|
||||||
|
|||||||
@@ -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.')
|
|
||||||
47
awx/main/management/commands/create_preload_data.py
Normal file
47
awx/main/management/commands/create_preload_data.py
Normal 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.')
|
||||||
51
awx/main/migrations/0016_v300_prompting_changes.py
Normal file
51
awx/main/migrations/0016_v300_prompting_changes.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
||||||
16
awx/main/migrations/0017_v300_prompting_migrations.py
Normal file
16
awx/main/migrations/0017_v300_prompting_migrations.py
Normal 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),
|
||||||
|
]
|
||||||
9
awx/main/migrations/_ask_for_variables.py
Normal file
9
awx/main/migrations/_ask_for_variables.py
Normal 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()
|
||||||
@@ -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):
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
295
awx/main/tests/functional/api/test_job_runtime_params.py
Normal file
295
awx/main/tests/functional/api/test_job_runtime_params.py
Normal 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
|
||||||
@@ -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")
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user