Merge branch 'devel' of https://github.com/ansible/ansible-tower into 11th-hour

This commit is contained in:
AlanCoding
2016-04-18 14:12:13 -04:00
29 changed files with 878 additions and 294 deletions

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',
related_name='%(class)ss',
blank=True,
null=True,
default=None,
on_delete=models.SET_NULL,
)
project = models.ForeignKey(
@@ -194,6 +196,26 @@ 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,
)
ask_credential_on_launch = models.BooleanField(
blank=True,
default=False,
)
survey_enabled = models.BooleanField(
default=False,
@@ -236,6 +258,15 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, ResourceMixin):
'force_handlers', 'skip_tags', 'start_at_task', 'become_enabled',
'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):
'''
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
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
def variables_needed_to_start(self):
@@ -365,6 +398,50 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, ResourceMixin):
kwargs['extra_vars'] = json.dumps(extra_vars)
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
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):

View File

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

View File

@@ -0,0 +1,299 @@
import pytest
import yaml
from awx.api.serializers import JobLaunchSerializer
from awx.main.models.credential import Credential
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.executor_role.members.add(admin_user)
job_template.inventory.save()
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.skip(reason="JT can_start without inventory needs to be fixed before passing")
@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('admin', False)
job_template.executor_role.members.add(common_user)
job_template.save()
job_template.project.member_role.members.add(common_user)
job_template.project.save()
credential = Credential.objects.get(pk=runtime_data['credential'])
credential.usage_role.members.add(common_user)
credential.save()
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
common_user = user('test-user', False)
deploy_jobtemplate.executor_role.members.add(common_user)
deploy_jobtemplate.save()
deploy_jobtemplate.inventory.usage_role.members.add(common_user)
deploy_jobtemplate.inventory.save()
deploy_jobtemplate.project.member_role.members.add(common_user)
deploy_jobtemplate.project.save()
deploy_jobtemplate.credential.usage_role.members.add(common_user)
deploy_jobtemplate.credential.save()
# 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():
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

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

View File

@@ -191,32 +191,23 @@ class CreateDefaultOrgTest(BaseCommandMixin, BaseTest):
def setUp(self):
super(CreateDefaultOrgTest, self).setUp()
self.setup_instances()
def test_create_default_org(self):
self.setup_users()
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.assertTrue('Default organization added' in stdout)
self.assertEqual(Organization.objects.count(), 1)
org = Organization.objects.all()[0]
self.assertEqual(org.created_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.assertFalse('Default organization added' in stdout)
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):
'''
Test cases for dumpdata management command.

View File

@@ -27,6 +27,8 @@ 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,
ask_credential_on_launch = True,
)
self.data_no_cred = dict(
name = 'launched job template no credential',
@@ -34,6 +36,8 @@ class JobTemplateLaunchTest(BaseJobTestMixin, django.test.TransactionTestCase):
inventory = self.inv_eng.pk,
project = self.proj_dev.pk,
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['name'] = 'launched job templated with ask passwords'
@@ -67,7 +71,7 @@ class JobTemplateLaunchTest(BaseJobTestMixin, django.test.TransactionTestCase):
def test_credential_implicit(self):
# Implicit, attached credentials
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'])
self.assertTrue(j.status == 'new')
@@ -75,7 +79,7 @@ class JobTemplateLaunchTest(BaseJobTestMixin, django.test.TransactionTestCase):
# Sending extra_vars as a JSON string, implicit credentials
with self.current_user(self.user_sue):
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'])
ev_dict = yaml.load(j.extra_vars)
self.assertIn('a', ev_dict)
@@ -86,7 +90,7 @@ class JobTemplateLaunchTest(BaseJobTestMixin, django.test.TransactionTestCase):
# Sending extra_vars as a JSON string, implicit credentials
with self.current_user(self.user_sue):
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'])
ev_dict = yaml.load(j.extra_vars)
self.assertIn('a', ev_dict)
@@ -97,7 +101,7 @@ class JobTemplateLaunchTest(BaseJobTestMixin, django.test.TransactionTestCase):
# Explicit, credential
with self.current_user(self.user_sue):
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'])
self.assertEqual(j.status, 'new')
self.assertEqual(j.credential.pk, self.cred_doug.pk)
@@ -106,7 +110,7 @@ class JobTemplateLaunchTest(BaseJobTestMixin, django.test.TransactionTestCase):
# Explicit, credential
with self.current_user(self.user_sue):
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'])
self.assertEqual(j.status, 'new')
self.assertEqual(j.credential.pk, self.cred_doug.pk)
@@ -114,7 +118,7 @@ class JobTemplateLaunchTest(BaseJobTestMixin, django.test.TransactionTestCase):
def test_credential_override(self):
# Explicit, credential
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'])
self.assertEqual(j.status, 'new')
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):
# Explicit, credential
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'])
self.assertEqual(j.status, 'new')
self.assertEqual(j.credential.pk, self.cred_doug.pk)
@@ -191,6 +195,7 @@ class JobTemplateLaunchPasswordsTest(BaseJobTestMixin, django.test.TransactionTe
project = self.proj_dev.pk,
credential = self.cred_sue_ask.pk,
playbook = self.proj_dev.playbooks[0],
ask_credential_on_launch = True,
)
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):
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'])
self.assertEqual(j.status, 'new')

View File

@@ -798,7 +798,7 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase):
self.assertEqual(jobs_qs.count(), 0)
# 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
# that we expect.
@@ -813,7 +813,7 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase):
self.assertEqual(job.hosts.all()[0], host)
# 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
# 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
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')
job = jobs_qs[0]
self.assertTrue("key" in job.extra_vars)
@@ -878,7 +878,7 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase):
break
self.assertTrue(host)
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)
job = jobs_qs[0]
self.assertEqual(job.launch_type, 'callback')
@@ -903,7 +903,7 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase):
break
self.assertTrue(host)
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)
job = jobs_qs[0]
self.assertEqual(job.launch_type, 'callback')
@@ -917,7 +917,7 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase):
host = host_qs[0]
host_ip = host.variables_dict['ansible_ssh_host']
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)
job = jobs_qs[0]
self.assertEqual(job.launch_type, 'callback')
@@ -951,7 +951,7 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase):
break
self.assertTrue(host)
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)
job = jobs_qs[0]
self.assertEqual(job.launch_type, 'callback')
@@ -1087,7 +1087,7 @@ class JobTransactionTest(BaseJobTestMixin, django.test.LiveServerTestCase):
response = self.get(url)
self.assertTrue(response['can_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)
self.assertEqual(job.status, 'successful', job.result_stdout)
self.assertFalse(errors)
@@ -1146,14 +1146,14 @@ class JobTemplateSurveyTest(BaseJobTestMixin, django.test.TransactionTestCase):
# should return, and should be able to launch template without error.
response = self.get(launch_url)
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
# job's extra vars.
self.post(url, json.loads(TEST_SIMPLE_REQUIRED_SURVEY), expect=200)
response = self.get(launch_url)
self.assertTrue(response['survey_enabled'])
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_extra = json.loads(job.extra_vars)
self.assertTrue("favorite_color" in job_extra)
@@ -1187,7 +1187,7 @@ class JobTemplateSurveyTest(BaseJobTestMixin, django.test.TransactionTestCase):
with self.current_user(self.user_sue):
response = self.post(url, json.loads(TEST_SURVEY_REQUIREMENTS), expect=200)
# 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
self.post(launch_url, dict(extra_vars=dict(long_answer='a', reqd_answer="foo")), expect=400)
# Long answer but requires a short answer
@@ -1199,9 +1199,9 @@ class JobTemplateSurveyTest(BaseJobTestMixin, django.test.TransactionTestCase):
# Integer that's too big
self.post(launch_url, dict(extra_vars=dict(int_answer=10, reqd_answer="foo")), expect=400)
# 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
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
self.post(launch_url, dict(extra_vars=dict(int_answer="test", reqd_answer="foo")), expect=400)
# Float that's too big
@@ -1209,7 +1209,7 @@ class JobTemplateSurveyTest(BaseJobTestMixin, django.test.TransactionTestCase):
# Float that's too small
self.post(launch_url, dict(extra_vars=dict(float_answer=1.995, reqd_answer="foo")), expect=400)
# 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
self.post(launch_url, dict(extra_vars=dict(float_answer="test", reqd_answer="foo")), expect=400)
# Wrong choice in single choice
@@ -1219,11 +1219,11 @@ class JobTemplateSurveyTest(BaseJobTestMixin, django.test.TransactionTestCase):
# Wrong type for multi choicen
self.post(launch_url, dict(extra_vars=dict(reqd_answer="foo", multi_choice="two")), expect=400)
# 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
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
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
with self.current_user(self.user_bob):