Add ability to prompt for several variable types on launch

This commit is contained in:
AlanCoding 2016-04-04 15:45:47 -04:00
parent 28ffa57258
commit cc84ed51d6
8 changed files with 291 additions and 6 deletions

View File

@ -1673,7 +1673,9 @@ class JobTemplateSerializer(UnifiedJobTemplateSerializer, JobOptionsSerializer):
class Meta:
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',
'survey_enabled', 'become_enabled')
def get_related(self, obj):
res = super(JobTemplateSerializer, self).get_related(obj)
@ -1730,10 +1732,16 @@ class JobSerializer(UnifiedJobSerializer, JobOptionsSerializer):
passwords_needed_to_start = 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:
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):
res = super(JobSerializer, self).get_related(obj)
@ -2102,9 +2110,13 @@ class JobLaunchSerializer(BaseSerializer):
class Meta:
model = JobTemplate
fields = ('can_start_without_user_input', 'passwords_needed_to_start', 'extra_vars',
'ask_variables_on_launch', 'survey_enabled', 'variables_needed_to_start',
'ask_variables_on_launch', 'ask_tags_on_launch', 'ask_job_type_on_launch',
'ask_inventory_on_launch', 'ask_limit_on_launch',
'survey_enabled', 'variables_needed_to_start',
'credential', 'credential_needed_to_start',)
read_only_fields = ('ask_variables_on_launch',)
read_only_fields = ('ask_variables_on_launch', 'ask_limit_on_launch',
'ask_tags_on_launch', 'ask_job_type_on_launch',
'ask_inventory_on_launch')
extra_kwargs = {
'credential': {
'write_only': True,
@ -2164,7 +2176,9 @@ class JobLaunchSerializer(BaseSerializer):
if errors:
raise serializers.ValidationError(errors)
JT_extra_vars = obj.extra_vars
attrs = super(JobLaunchSerializer, self).validate(attrs)
obj.extra_vars = JT_extra_vars
return attrs
class NotifierSerializer(BaseSerializer):

View File

@ -2115,8 +2115,60 @@ class JobTemplateLaunch(RetrieveAPIView, GenericAPIView):
kv = {
'credential': serializer.instance.credential.pk,
}
# -- following code will be moved to JobTemplate model --
# Sort the runtime fields allowed and disallowed by job template
ignored_fields = {}
if 'extra_vars' in request.data:
kv['extra_vars'] = request.data['extra_vars']
kv['extra_vars'] = {}
ignored_fields['extra_vars'] = {}
if obj.ask_variables_on_launch:
# Accept all extra_vars if the flag is set
kv['extra_vars'] = request.data['extra_vars']
else:
if obj.survey_enabled:
# Accept vars defined in the survey and no others
survey_vars = [question['variable'] for question in obj.survey_spec['spec']]
for key in request.data['extra_vars']:
if key in survey_vars:
kv['extra_vars'][key] = request.data['extra_vars'][key]
else:
ignored_fields['extra_vars'][key] = request.data['extra_vars'][key]
else:
# No survey & prompt flag is false - ignore all
ignored_fields['extra_vars'] = request.data['extra_vars']
if 'limit' in request.data:
if obj.ask_limit_on_launch:
kv['limit'] = request.data['limit']
else:
ignored_fields['limit'] = request.data['limit']
if 'job_tags' or 'skip_tags' in request.data:
if obj.ask_tags_on_launch:
if 'job_tags' in request.data:
kv['job_tags'] = request.data['job_tags']
if 'skip_tags' in request.data:
kv['skip_tags'] = request.data['skip_tags']
else:
if 'job_tags' in request.data:
ignored_fields['job_tags'] = request.data['job_tags']
if 'skip_tags' in request.data:
ignored_fields['skip_tags'] = request.data['skip_tags']
if 'job_type' in request.data:
if obj.ask_job_type_on_launch:
kv['job_type'] = request.data['job_type']
else:
ignored_fields['job_type'] = request.data['job_type']
if 'inventory' in request.data:
inv_id = request.data['inventory']
if obj.ask_inventory_on_launch and Inventory.objects.get(pk=inv_id).accessible_by(self.request.user, {'write': True}):
kv['inventory'] = inv_id
else:
ignored_fields['inventory'] = inv_id
kv.update(passwords)
new_job = obj.create_unified_job(**kv)
@ -2127,6 +2179,9 @@ class JobTemplateLaunch(RetrieveAPIView, GenericAPIView):
return Response(data, status=status.HTTP_400_BAD_REQUEST)
else:
data = dict(job=new_job.id)
serializer = JobSerializer(new_job)
data['job_data'] = serializer.data
data['ignored_fields'] = ignored_fields
return Response(data, status=status.HTTP_202_ACCEPTED)
class JobTemplateSchedulesList(SubListCreateAttachDetachAPIView):

View File

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('main', '0012_v300_create_labels'),
]
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_job_type_on_launch',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='jobtemplate',
name='ask_tags_on_launch',
field=models.BooleanField(default=False),
),
]

View File

@ -194,6 +194,22 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, ResourceMixin):
blank=True,
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,
)
survey_enabled = models.BooleanField(
default=False,

View File

@ -0,0 +1,159 @@
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
@pytest.fixture
def runtime_data():
cred_obj = Credential.objects.create(name='runtime-cred', kind='ssh', username='test_user2', password='pas4word2')
return dict(
limit='test-servers',
job_type='check',
inventory=cred_obj.pk,
job_tags='["provision"]',
skip_tags='["restart"]',
extra_vars='{"job_launch_var": 4}'
)
@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,
)
return rf
# Probably remove this test after development is finished
@pytest.mark.django_db
def test_job_launch_prompts_echo(job_template_prompts, get, user):
job_template = job_template_prompts(True)
assert job_template.ask_variables_on_launch
url = reverse('api:job_template_launch', args=[job_template.pk])
response = get(
reverse('api:job_template_launch', args=[job_template.pk]),
user('admin', True))
# Just checking that the GET response has what we expect
assert response.data['ask_variables_on_launch']
assert response.data['ask_tags_on_launch']
assert response.data['ask_job_type_on_launch']
assert response.data['ask_inventory_on_launch']
assert response.data['ask_limit_on_launch']
@pytest.mark.django_db
def test_job_ignore_unprompted_vars(runtime_data, job_template_prompts, post, user):
job_template = job_template_prompts(False)
response = post(
reverse('api:job_template_launch', args=[job_template.pk]),
runtime_data, user('admin', True))
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.limit
assert job_obj.job_type == job_template.job_type
assert job_obj.inventory.pk == job_template.inventory.pk
assert job_obj.job_tags == job_template.job_tags
# 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 'job_tags' in response.data['ignored_fields']
assert 'skip_tags' in response.data['ignored_fields']
@pytest.mark.django_db
def test_job_accept_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]),
runtime_data, user('admin', True))
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.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.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
@pytest.mark.django_db
def test_job_launch_unprompted_vars_with_survey(job_template_prompts, post, user):
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))
job_id = response.data['job']
job_obj = Job.objects.get(pk=job_id)
# Check that the survey variable is accept 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
# To add:
# permissions testing (can't provide inventory you can't run against)
# credentials/password test if they will be included in response format

View File

@ -167,6 +167,10 @@ def organization_factory(instance):
def credential():
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
def inventory(organization):
return organization.inventories.create(name="test-inv")

View File

@ -27,6 +27,7 @@ class JobTemplateLaunchTest(BaseJobTestMixin, django.test.TransactionTestCase):
project = self.proj_dev.pk,
credential = self.cred_sue.pk,
playbook = self.proj_dev.playbooks[0],
ask_variables_on_launch = True,
)
self.data_no_cred = dict(
name = 'launched job template no credential',

View File

@ -19,7 +19,8 @@ else
echo "Failed to find tower source tree, map your development tree volume"
fi
cp -nR /tmp/ansible_tower.egg-info /tower_devel/ || true
# will remove before PR lands
cp -fR /tmp/ansible_tower.egg-info /tower_devel/ || true
# Check if we need to build dependencies
#if [ -f "awx/lib/.deps_built" ]; then