From eddaf1e97bc3d9b70a6c049cb5301a6f11c4d3c6 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Wed, 18 Feb 2015 11:49:35 -0500 Subject: [PATCH 01/26] fixing popover bug --- awx/ui/static/lib/ansible/directives.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/awx/ui/static/lib/ansible/directives.js b/awx/ui/static/lib/ansible/directives.js index cf97f9d8e8..8e539685a1 100644 --- a/awx/ui/static/lib/ansible/directives.js +++ b/awx/ui/static/lib/ansible/directives.js @@ -534,15 +534,23 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job $(element).one('click', showPopover); - $(element).on('shown.bs.popover', function() { + function bindPopoverDismiss() { $('body').one('click.popover' + id_to_close, function(e) { if ($(e.target).parents(id_to_close).length === 0) { + // case: you clicked to open the popover and then you + // clicked outside of it...hide it. $(element).popover('hide'); + } else { + // case: you clicked to open the popover and then you + // clicked inside the popover + bindPopoverDismiss(); } }); + } + $(element).on('shown.bs.popover', function() { + bindPopoverDismiss(); $(document).on('keydown.popover', dismissOnEsc); - }); $(element).on('hidden.bs.popover', function() { From a67b2e29b6bc1a9954d6104f9ed418fbf64275a4 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Wed, 18 Feb 2015 14:32:09 -0500 Subject: [PATCH 02/26] JSHint removed trailing whitespace --- awx/ui/static/lib/ansible/Socket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/static/lib/ansible/Socket.js b/awx/ui/static/lib/ansible/Socket.js index 486ec5a7d8..a774954a82 100644 --- a/awx/ui/static/lib/ansible/Socket.js +++ b/awx/ui/static/lib/ansible/Socket.js @@ -71,7 +71,7 @@ angular.module('SocketIO', ['AuthService', 'Utilities']) $log.debug('Socket connecting to: ' + url); self.scope.socket_url = url; self.socket = io.connect(url, { - query: "Token="+token, + query: "Token="+token, headers: { 'Authorization': 'Token ' + token, // i don't think these are actually inserted into the header--jt From 9f9fc3f8b313bc4128c7298c211e9857d2a28680 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Wed, 18 Feb 2015 14:50:54 -0500 Subject: [PATCH 03/26] break up tests.jobs and fix awx.api.tests --- awx/main/tests/jobs/__init__.py | 6 +- awx/main/tests/jobs/base.py | 515 ++++++++++++++++++ .../jobs/{jobs.py => jobs_monolithic.py} | 512 +---------------- 3 files changed, 519 insertions(+), 514 deletions(-) create mode 100644 awx/main/tests/jobs/base.py rename awx/main/tests/jobs/{jobs.py => jobs_monolithic.py} (74%) diff --git a/awx/main/tests/jobs/__init__.py b/awx/main/tests/jobs/__init__.py index 0087044beb..73064040b6 100644 --- a/awx/main/tests/jobs/__init__.py +++ b/awx/main/tests/jobs/__init__.py @@ -1,3 +1,3 @@ - -from awx.main.tests.jobs.jobs import * -from awx.main.tests.jobs.survey_password import * \ No newline at end of file +from jobs_monolithic import * +from survey_password import * +from base import * \ No newline at end of file diff --git a/awx/main/tests/jobs/base.py b/awx/main/tests/jobs/base.py new file mode 100644 index 0000000000..d13ef0a37c --- /dev/null +++ b/awx/main/tests/jobs/base.py @@ -0,0 +1,515 @@ +# Python +import uuid + +# AWX +from awx.main.models import * # noqa +from awx.main.tests.base import BaseTestMixin + +TEST_PLAYBOOK = '''- hosts: all + gather_facts: false + tasks: + - name: woohoo + command: test 1 = 1 +''' + +class BaseJobTestMixin(BaseTestMixin): + + + def _create_inventory(self, name, organization, created_by, + groups_hosts_dict): + '''Helper method for creating inventory with groups and hosts.''' + inventory = organization.inventories.create( + name=name, + created_by=created_by, + ) + for group_name, host_names in groups_hosts_dict.items(): + group = inventory.groups.create( + name=group_name, + created_by=created_by, + ) + for host_name in host_names: + host = inventory.hosts.create( + name=host_name, + created_by=created_by, + ) + group.hosts.add(host) + return inventory + + def populate(self): + # Here's a little story about the Ansible Bread Company, or ABC. They + # make machines that make bread - bakers, slicers, and packagers - and + # these machines are each controlled by a Linux boxes, which is in turn + # managed by Ansible Commander. + + # Sue is the super user. You don't mess with Sue or you're toast. Ha. + self.user_sue = self.make_user('sue', super_user=True) + + # There are three organizations in ABC using Ansible, since it's the + # best thing for dev ops automation since, well, sliced bread. + + # Engineering - They design and build the machines. + self.org_eng = Organization.objects.create( + name='engineering', + created_by=self.user_sue, + ) + # Support - They fix it when it's not working. + self.org_sup = Organization.objects.create( + name='support', + created_by=self.user_sue, + ) + # Operations - They implement the production lines using the machines. + self.org_ops = Organization.objects.create( + name='operations', + created_by=self.user_sue, + ) + + # Alex is Sue's IT assistant who can also administer all of the + # organizations. + self.user_alex = self.make_user('alex') + self.org_eng.admins.add(self.user_alex) + self.org_sup.admins.add(self.user_alex) + self.org_ops.admins.add(self.user_alex) + + # Bob is the head of engineering. He's an admin for engineering, but + # also a user within the operations organization (so he can see the + # results if things go wrong in production). + self.user_bob = self.make_user('bob') + self.org_eng.admins.add(self.user_bob) + self.org_ops.users.add(self.user_bob) + + # Chuck is the lead engineer. He has full reign over engineering, but + # no other organizations. + self.user_chuck = self.make_user('chuck') + self.org_eng.admins.add(self.user_chuck) + + # Doug is the other engineer working under Chuck. He can write + # playbooks and check them, but Chuck doesn't quite think he's ready to + # run them yet. Poor Doug. + self.user_doug = self.make_user('doug') + self.org_eng.users.add(self.user_doug) + + # Juan is another engineer working under Chuck. He has a little more freedom + # to run playbooks but can't create job templates + self.user_juan = self.make_user('juan') + self.org_eng.users.add(self.user_juan) + + # Hannibal is Chuck's right-hand man. Chuck usually has him create the job + # templates that the rest of the team will use + self.user_hannibal = self.make_user('hannibal') + self.org_eng.users.add(self.user_hannibal) + + # Eve is the head of support. She can also see what goes on in + # operations to help them troubleshoot problems. + self.user_eve = self.make_user('eve') + self.org_sup.admins.add(self.user_eve) + self.org_ops.users.add(self.user_eve) + + # Frank is the other support guy. + self.user_frank = self.make_user('frank') + self.org_sup.users.add(self.user_frank) + + # Greg is the head of operations. + self.user_greg = self.make_user('greg') + self.org_ops.admins.add(self.user_greg) + + # Holly is an operations engineer. + self.user_holly = self.make_user('holly') + self.org_ops.users.add(self.user_holly) + + # Iris is another operations engineer. + self.user_iris = self.make_user('iris') + self.org_ops.users.add(self.user_iris) + + # Randall and Billybob are new ops interns that ops uses to test + # their playbooks and inventory + self.user_randall = self.make_user('randall') + self.org_ops.users.add(self.user_randall) + + # He works with Randall + self.user_billybob = self.make_user('billybob') + self.org_ops.users.add(self.user_billybob) + + # Jim is the newest intern. He can login, but can't do anything quite yet + # except make everyone else fresh coffee. + self.user_jim = self.make_user('jim') + + # There are three main projects, one each for the development, test and + # production branches of the playbook repository. All three orgs can + # use the production branch, support can use the production and testing + # branches, and operations can only use the production branch. + self.proj_dev = self.make_project('dev', 'development branch', + self.user_sue, TEST_PLAYBOOK) + self.org_eng.projects.add(self.proj_dev) + self.proj_test = self.make_project('test', 'testing branch', + self.user_sue, TEST_PLAYBOOK) + self.org_eng.projects.add(self.proj_test) + self.org_sup.projects.add(self.proj_test) + self.proj_prod = self.make_project('prod', 'production branch', + self.user_sue, TEST_PLAYBOOK) + self.org_eng.projects.add(self.proj_prod) + self.org_sup.projects.add(self.proj_prod) + self.org_ops.projects.add(self.proj_prod) + + # Operations also has 2 additional projects specific to the east/west + # production environments. + self.proj_prod_east = self.make_project('prod-east', + 'east production branch', + self.user_sue, TEST_PLAYBOOK) + self.org_ops.projects.add(self.proj_prod_east) + self.proj_prod_west = self.make_project('prod-west', + 'west production branch', + self.user_sue, TEST_PLAYBOOK) + self.org_ops.projects.add(self.proj_prod_west) + + # The engineering organization has a set of servers to use for + # development and testing (2 bakers, 1 slicer, 1 packager). + self.inv_eng = self._create_inventory( + name='engineering environment', + organization=self.org_eng, + created_by=self.user_sue, + groups_hosts_dict={ + 'bakers': ['eng-baker1', 'eng-baker2'], + 'slicers': ['eng-slicer1'], + 'packagers': ['eng-packager1'], + }, + ) + + # The support organization has a set of servers to use for + # testing and reproducing problems from operations (1 baker, 1 slicer, + # 1 packager). + self.inv_sup = self._create_inventory( + name='support environment', + organization=self.org_sup, + created_by=self.user_sue, + groups_hosts_dict={ + 'bakers': ['sup-baker1'], + 'slicers': ['sup-slicer1'], + 'packagers': ['sup-packager1'], + }, + ) + + # The operations organization manages multiple sets of servers for the + # east and west production facilities. + self.inv_ops_east = self._create_inventory( + name='east production environment', + organization=self.org_ops, + created_by=self.user_sue, + groups_hosts_dict={ + 'bakers': ['east-baker%d' % n for n in range(1, 4)], + 'slicers': ['east-slicer%d' % n for n in range(1, 3)], + 'packagers': ['east-packager%d' % n for n in range(1, 3)], + }, + ) + self.inv_ops_west = self._create_inventory( + name='west production environment', + organization=self.org_ops, + created_by=self.user_sue, + groups_hosts_dict={ + 'bakers': ['west-baker%d' % n for n in range(1, 6)], + 'slicers': ['west-slicer%d' % n for n in range(1, 4)], + 'packagers': ['west-packager%d' % n for n in range(1, 3)], + }, + ) + + # Operations is divided into teams to work on the east/west servers. + # Greg and Holly work on east, Greg and iris work on west. + self.team_ops_east = self.org_ops.teams.create( + name='easterners', + created_by=self.user_sue) + self.team_ops_east.projects.add(self.proj_prod) + self.team_ops_east.projects.add(self.proj_prod_east) + self.team_ops_east.users.add(self.user_greg) + self.team_ops_east.users.add(self.user_holly) + self.team_ops_west = self.org_ops.teams.create( + name='westerners', + created_by=self.user_sue) + self.team_ops_west.projects.add(self.proj_prod) + self.team_ops_west.projects.add(self.proj_prod_west) + self.team_ops_west.users.add(self.user_greg) + self.team_ops_west.users.add(self.user_iris) + + # The south team is no longer active having been folded into the east team + self.team_ops_south = self.org_ops.teams.create( + name='southerners', + created_by=self.user_sue, + active=False, + ) + self.team_ops_south.projects.add(self.proj_prod) + self.team_ops_south.users.add(self.user_greg) + + # The north team is going to be deleted + self.team_ops_north = self.org_ops.teams.create( + name='northerners', + created_by=self.user_sue, + ) + self.team_ops_north.projects.add(self.proj_prod) + self.team_ops_north.users.add(self.user_greg) + + # The testers team are interns that can only check playbooks but can't + # run them + self.team_ops_testers = self.org_ops.teams.create( + name='testers', + created_by=self.user_sue, + ) + self.team_ops_testers.projects.add(self.proj_prod) + self.team_ops_testers.users.add(self.user_randall) + self.team_ops_testers.users.add(self.user_billybob) + + # Each user has his/her own set of credentials. + from awx.main.tests.tasks import (TEST_SSH_KEY_DATA, + TEST_SSH_KEY_DATA_LOCKED, + TEST_SSH_KEY_DATA_UNLOCK) + self.cred_sue = self.user_sue.credentials.create( + username='sue', + password=TEST_SSH_KEY_DATA, + created_by=self.user_sue, + ) + self.cred_bob = self.user_bob.credentials.create( + username='bob', + password='ASK', + created_by=self.user_sue, + ) + self.cred_chuck = self.user_chuck.credentials.create( + username='chuck', + ssh_key_data=TEST_SSH_KEY_DATA, + created_by=self.user_sue, + ) + self.cred_doug = self.user_doug.credentials.create( + username='doug', + password='doug doesn\'t mind his password being saved. this ' + 'is why we dont\'t let doug actually run jobs.', + created_by=self.user_sue, + ) + self.cred_eve = self.user_eve.credentials.create( + username='eve', + password='ASK', + sudo_username='root', + sudo_password='ASK', + created_by=self.user_sue, + ) + self.cred_frank = self.user_frank.credentials.create( + username='frank', + password='fr@nk the t@nk', + created_by=self.user_sue, + ) + self.cred_greg = self.user_greg.credentials.create( + username='greg', + ssh_key_data=TEST_SSH_KEY_DATA_LOCKED, + ssh_key_unlock='ASK', + created_by=self.user_sue, + ) + self.cred_holly = self.user_holly.credentials.create( + username='holly', + password='holly rocks', + created_by=self.user_sue, + ) + self.cred_iris = self.user_iris.credentials.create( + username='iris', + password='ASK', + created_by=self.user_sue, + ) + + # Each operations team also has shared credentials they can use. + self.cred_ops_east = self.team_ops_east.credentials.create( + username='east', + ssh_key_data=TEST_SSH_KEY_DATA_LOCKED, + ssh_key_unlock=TEST_SSH_KEY_DATA_UNLOCK, + created_by = self.user_sue, + ) + self.cred_ops_west = self.team_ops_west.credentials.create( + username='west', + password='Heading270', + created_by = self.user_sue, + ) + self.cred_ops_south = self.team_ops_south.credentials.create( + username='south', + password='Heading180', + created_by = self.user_sue, + ) + + self.cred_ops_north = self.team_ops_north.credentials.create( + username='north', + password='Heading0', + created_by = self.user_sue, + ) + + self.cred_ops_test = self.team_ops_testers.credentials.create( + username='testers', + password='HeadingNone', + created_by = self.user_sue, + ) + + self.ops_testers_permission = Permission.objects.create( + inventory = self.inv_ops_west, + project = self.proj_prod, + team = self.team_ops_testers, + permission_type = PERM_INVENTORY_CHECK, + created_by = self.user_sue + ) + + self.doug_check_permission = Permission.objects.create( + inventory = self.inv_eng, + project = self.proj_dev, + user = self.user_doug, + permission_type = PERM_INVENTORY_CHECK, + created_by = self.user_sue + ) + + self.juan_deploy_permission = Permission.objects.create( + inventory = self.inv_eng, + project = self.proj_dev, + user = self.user_juan, + permission_type = PERM_INVENTORY_DEPLOY, + created_by = self.user_sue + ) + + self.hannibal_create_permission = Permission.objects.create( + inventory = self.inv_eng, + project = self.proj_dev, + user = self.user_hannibal, + permission_type = PERM_JOBTEMPLATE_CREATE, + created_by = self.user_sue + ) + + # FIXME: Define explicit permissions for tests. + # other django user is on the project team and can deploy + #self.permission1 = Permission.objects.create( + # inventory = self.inventory, + # project = self.project, + # team = self.team, + # permission_type = PERM_INVENTORY_DEPLOY, + # created_by = self.normal_django_user + #) + # individual permission granted to other2 user, can run check mode + #self.permission2 = Permission.objects.create( + # inventory = self.inventory, + # project = self.project, + # user = self.other2_django_user, + # permission_type = PERM_INVENTORY_CHECK, + # created_by = self.normal_django_user + #) + + # Engineering has job templates to check/run the dev project onto + # their own inventory. + self.jt_eng_check = JobTemplate.objects.create( + name='eng-dev-check', + job_type='check', + inventory= self.inv_eng, + project=self.proj_dev, + playbook=self.proj_dev.playbooks[0], + host_config_key=uuid.uuid4().hex, + created_by=self.user_sue, + ) + # self.job_eng_check = self.jt_eng_check.create_job( + # created_by=self.user_sue, + # credential=self.cred_doug, + # ) + self.jt_eng_run = JobTemplate.objects.create( + name='eng-dev-run', + job_type='run', + inventory= self.inv_eng, + project=self.proj_dev, + playbook=self.proj_dev.playbooks[0], + host_config_key=uuid.uuid4().hex, + created_by=self.user_sue, + ) + # self.job_eng_run = self.jt_eng_run.create_job( + # created_by=self.user_sue, + # credential=self.cred_chuck, + # ) + + # Support has job templates to check/run the test project onto + # their own inventory. + self.jt_sup_check = JobTemplate.objects.create( + name='sup-test-check', + job_type='check', + inventory= self.inv_sup, + project=self.proj_test, + playbook=self.proj_test.playbooks[0], + host_config_key=uuid.uuid4().hex, + created_by=self.user_sue, + ) + # self.job_sup_check = self.jt_sup_check.create_job( + # created_by=self.user_sue, + # credential=self.cred_frank, + # ) + self.jt_sup_run = JobTemplate.objects.create( + name='sup-test-run', + job_type='run', + inventory= self.inv_sup, + project=self.proj_test, + playbook=self.proj_test.playbooks[0], + host_config_key=uuid.uuid4().hex, + credential=self.cred_eve, + created_by=self.user_sue, + ) + # self.job_sup_run = self.jt_sup_run.create_job( + # created_by=self.user_sue, + # ) + + # Operations has job templates to check/run the prod project onto + # both east and west inventories, by default using the team credential. + self.jt_ops_east_check = JobTemplate.objects.create( + name='ops-east-prod-check', + job_type='check', + inventory= self.inv_ops_east, + project=self.proj_prod, + playbook=self.proj_prod.playbooks[0], + credential=self.cred_ops_east, + host_config_key=uuid.uuid4().hex, + created_by=self.user_sue, + ) + # self.job_ops_east_check = self.jt_ops_east_check.create_job( + # created_by=self.user_sue, + # ) + self.jt_ops_east_run = JobTemplate.objects.create( + name='ops-east-prod-run', + job_type='run', + inventory= self.inv_ops_east, + project=self.proj_prod, + playbook=self.proj_prod.playbooks[0], + credential=self.cred_ops_east, + host_config_key=uuid.uuid4().hex, + created_by=self.user_sue, + ) + # self.job_ops_east_run = self.jt_ops_east_run.create_job( + # created_by=self.user_sue, + # ) + self.jt_ops_west_check = JobTemplate.objects.create( + name='ops-west-prod-check', + job_type='check', + inventory= self.inv_ops_west, + project=self.proj_prod, + playbook=self.proj_prod.playbooks[0], + credential=self.cred_ops_west, + host_config_key=uuid.uuid4().hex, + created_by=self.user_sue, + ) + # self.job_ops_west_check = self.jt_ops_west_check.create_job( + # created_by=self.user_sue, + # ) + self.jt_ops_west_run = JobTemplate.objects.create( + name='ops-west-prod-run', + job_type='run', + inventory= self.inv_ops_west, + project=self.proj_prod, + playbook=self.proj_prod.playbooks[0], + credential=self.cred_ops_west, + host_config_key=uuid.uuid4().hex, + created_by=self.user_sue, + ) + # self.job_ops_west_run = self.jt_ops_west_run.create_job( + # created_by=self.user_sue, + # ) + + def setUp(self): + super(BaseJobTestMixin, self).setUp() + self.start_redis() + self.setup_instances() + self.populate() + self.start_queue() + + def tearDown(self): + super(BaseJobTestMixin, self).tearDown() + self.stop_redis() + self.terminate_queue() diff --git a/awx/main/tests/jobs/jobs.py b/awx/main/tests/jobs/jobs_monolithic.py similarity index 74% rename from awx/main/tests/jobs/jobs.py rename to awx/main/tests/jobs/jobs_monolithic.py index 89826e782c..f56db77a66 100644 --- a/awx/main/tests/jobs/jobs.py +++ b/awx/main/tests/jobs/jobs_monolithic.py @@ -9,7 +9,6 @@ import struct import threading import time import urlparse -import uuid # Django import django.test @@ -24,18 +23,11 @@ import requests # AWX from awx.main.models import * # noqa -from awx.main.tests.base import BaseTestMixin +from base import BaseJobTestMixin __all__ = ['JobTemplateTest', 'JobTest', 'JobStartCancelTest', 'JobTemplateCallbackTest', 'JobTransactionTest', 'JobTemplateSurveyTest'] -TEST_PLAYBOOK = '''- hosts: all - gather_facts: false - tasks: - - name: woohoo - command: test 1 = 1 -''' - TEST_ASYNC_PLAYBOOK = ''' - hosts: all gather_facts: false @@ -193,508 +185,6 @@ TEST_SURVEY_REQUIREMENTS = ''' } ''' -class BaseJobTestMixin(BaseTestMixin): - - - def _create_inventory(self, name, organization, created_by, - groups_hosts_dict): - '''Helper method for creating inventory with groups and hosts.''' - inventory = organization.inventories.create( - name=name, - created_by=created_by, - ) - for group_name, host_names in groups_hosts_dict.items(): - group = inventory.groups.create( - name=group_name, - created_by=created_by, - ) - for host_name in host_names: - host = inventory.hosts.create( - name=host_name, - created_by=created_by, - ) - group.hosts.add(host) - return inventory - - def populate(self): - # Here's a little story about the Ansible Bread Company, or ABC. They - # make machines that make bread - bakers, slicers, and packagers - and - # these machines are each controlled by a Linux boxes, which is in turn - # managed by Ansible Commander. - - # Sue is the super user. You don't mess with Sue or you're toast. Ha. - self.user_sue = self.make_user('sue', super_user=True) - - # There are three organizations in ABC using Ansible, since it's the - # best thing for dev ops automation since, well, sliced bread. - - # Engineering - They design and build the machines. - self.org_eng = Organization.objects.create( - name='engineering', - created_by=self.user_sue, - ) - # Support - They fix it when it's not working. - self.org_sup = Organization.objects.create( - name='support', - created_by=self.user_sue, - ) - # Operations - They implement the production lines using the machines. - self.org_ops = Organization.objects.create( - name='operations', - created_by=self.user_sue, - ) - - # Alex is Sue's IT assistant who can also administer all of the - # organizations. - self.user_alex = self.make_user('alex') - self.org_eng.admins.add(self.user_alex) - self.org_sup.admins.add(self.user_alex) - self.org_ops.admins.add(self.user_alex) - - # Bob is the head of engineering. He's an admin for engineering, but - # also a user within the operations organization (so he can see the - # results if things go wrong in production). - self.user_bob = self.make_user('bob') - self.org_eng.admins.add(self.user_bob) - self.org_ops.users.add(self.user_bob) - - # Chuck is the lead engineer. He has full reign over engineering, but - # no other organizations. - self.user_chuck = self.make_user('chuck') - self.org_eng.admins.add(self.user_chuck) - - # Doug is the other engineer working under Chuck. He can write - # playbooks and check them, but Chuck doesn't quite think he's ready to - # run them yet. Poor Doug. - self.user_doug = self.make_user('doug') - self.org_eng.users.add(self.user_doug) - - # Juan is another engineer working under Chuck. He has a little more freedom - # to run playbooks but can't create job templates - self.user_juan = self.make_user('juan') - self.org_eng.users.add(self.user_juan) - - # Hannibal is Chuck's right-hand man. Chuck usually has him create the job - # templates that the rest of the team will use - self.user_hannibal = self.make_user('hannibal') - self.org_eng.users.add(self.user_hannibal) - - # Eve is the head of support. She can also see what goes on in - # operations to help them troubleshoot problems. - self.user_eve = self.make_user('eve') - self.org_sup.admins.add(self.user_eve) - self.org_ops.users.add(self.user_eve) - - # Frank is the other support guy. - self.user_frank = self.make_user('frank') - self.org_sup.users.add(self.user_frank) - - # Greg is the head of operations. - self.user_greg = self.make_user('greg') - self.org_ops.admins.add(self.user_greg) - - # Holly is an operations engineer. - self.user_holly = self.make_user('holly') - self.org_ops.users.add(self.user_holly) - - # Iris is another operations engineer. - self.user_iris = self.make_user('iris') - self.org_ops.users.add(self.user_iris) - - # Randall and Billybob are new ops interns that ops uses to test - # their playbooks and inventory - self.user_randall = self.make_user('randall') - self.org_ops.users.add(self.user_randall) - - # He works with Randall - self.user_billybob = self.make_user('billybob') - self.org_ops.users.add(self.user_billybob) - - # Jim is the newest intern. He can login, but can't do anything quite yet - # except make everyone else fresh coffee. - self.user_jim = self.make_user('jim') - - # There are three main projects, one each for the development, test and - # production branches of the playbook repository. All three orgs can - # use the production branch, support can use the production and testing - # branches, and operations can only use the production branch. - self.proj_dev = self.make_project('dev', 'development branch', - self.user_sue, TEST_PLAYBOOK) - self.org_eng.projects.add(self.proj_dev) - self.proj_test = self.make_project('test', 'testing branch', - self.user_sue, TEST_PLAYBOOK) - self.org_eng.projects.add(self.proj_test) - self.org_sup.projects.add(self.proj_test) - self.proj_prod = self.make_project('prod', 'production branch', - self.user_sue, TEST_PLAYBOOK) - self.org_eng.projects.add(self.proj_prod) - self.org_sup.projects.add(self.proj_prod) - self.org_ops.projects.add(self.proj_prod) - - # Operations also has 2 additional projects specific to the east/west - # production environments. - self.proj_prod_east = self.make_project('prod-east', - 'east production branch', - self.user_sue, TEST_PLAYBOOK) - self.org_ops.projects.add(self.proj_prod_east) - self.proj_prod_west = self.make_project('prod-west', - 'west production branch', - self.user_sue, TEST_PLAYBOOK) - self.org_ops.projects.add(self.proj_prod_west) - - # The engineering organization has a set of servers to use for - # development and testing (2 bakers, 1 slicer, 1 packager). - self.inv_eng = self._create_inventory( - name='engineering environment', - organization=self.org_eng, - created_by=self.user_sue, - groups_hosts_dict={ - 'bakers': ['eng-baker1', 'eng-baker2'], - 'slicers': ['eng-slicer1'], - 'packagers': ['eng-packager1'], - }, - ) - - # The support organization has a set of servers to use for - # testing and reproducing problems from operations (1 baker, 1 slicer, - # 1 packager). - self.inv_sup = self._create_inventory( - name='support environment', - organization=self.org_sup, - created_by=self.user_sue, - groups_hosts_dict={ - 'bakers': ['sup-baker1'], - 'slicers': ['sup-slicer1'], - 'packagers': ['sup-packager1'], - }, - ) - - # The operations organization manages multiple sets of servers for the - # east and west production facilities. - self.inv_ops_east = self._create_inventory( - name='east production environment', - organization=self.org_ops, - created_by=self.user_sue, - groups_hosts_dict={ - 'bakers': ['east-baker%d' % n for n in range(1, 4)], - 'slicers': ['east-slicer%d' % n for n in range(1, 3)], - 'packagers': ['east-packager%d' % n for n in range(1, 3)], - }, - ) - self.inv_ops_west = self._create_inventory( - name='west production environment', - organization=self.org_ops, - created_by=self.user_sue, - groups_hosts_dict={ - 'bakers': ['west-baker%d' % n for n in range(1, 6)], - 'slicers': ['west-slicer%d' % n for n in range(1, 4)], - 'packagers': ['west-packager%d' % n for n in range(1, 3)], - }, - ) - - # Operations is divided into teams to work on the east/west servers. - # Greg and Holly work on east, Greg and iris work on west. - self.team_ops_east = self.org_ops.teams.create( - name='easterners', - created_by=self.user_sue) - self.team_ops_east.projects.add(self.proj_prod) - self.team_ops_east.projects.add(self.proj_prod_east) - self.team_ops_east.users.add(self.user_greg) - self.team_ops_east.users.add(self.user_holly) - self.team_ops_west = self.org_ops.teams.create( - name='westerners', - created_by=self.user_sue) - self.team_ops_west.projects.add(self.proj_prod) - self.team_ops_west.projects.add(self.proj_prod_west) - self.team_ops_west.users.add(self.user_greg) - self.team_ops_west.users.add(self.user_iris) - - # The south team is no longer active having been folded into the east team - self.team_ops_south = self.org_ops.teams.create( - name='southerners', - created_by=self.user_sue, - active=False, - ) - self.team_ops_south.projects.add(self.proj_prod) - self.team_ops_south.users.add(self.user_greg) - - # The north team is going to be deleted - self.team_ops_north = self.org_ops.teams.create( - name='northerners', - created_by=self.user_sue, - ) - self.team_ops_north.projects.add(self.proj_prod) - self.team_ops_north.users.add(self.user_greg) - - # The testers team are interns that can only check playbooks but can't - # run them - self.team_ops_testers = self.org_ops.teams.create( - name='testers', - created_by=self.user_sue, - ) - self.team_ops_testers.projects.add(self.proj_prod) - self.team_ops_testers.users.add(self.user_randall) - self.team_ops_testers.users.add(self.user_billybob) - - # Each user has his/her own set of credentials. - from awx.main.tests.tasks import (TEST_SSH_KEY_DATA, - TEST_SSH_KEY_DATA_LOCKED, - TEST_SSH_KEY_DATA_UNLOCK) - self.cred_sue = self.user_sue.credentials.create( - username='sue', - password=TEST_SSH_KEY_DATA, - created_by=self.user_sue, - ) - self.cred_bob = self.user_bob.credentials.create( - username='bob', - password='ASK', - created_by=self.user_sue, - ) - self.cred_chuck = self.user_chuck.credentials.create( - username='chuck', - ssh_key_data=TEST_SSH_KEY_DATA, - created_by=self.user_sue, - ) - self.cred_doug = self.user_doug.credentials.create( - username='doug', - password='doug doesn\'t mind his password being saved. this ' - 'is why we dont\'t let doug actually run jobs.', - created_by=self.user_sue, - ) - self.cred_eve = self.user_eve.credentials.create( - username='eve', - password='ASK', - sudo_username='root', - sudo_password='ASK', - created_by=self.user_sue, - ) - self.cred_frank = self.user_frank.credentials.create( - username='frank', - password='fr@nk the t@nk', - created_by=self.user_sue, - ) - self.cred_greg = self.user_greg.credentials.create( - username='greg', - ssh_key_data=TEST_SSH_KEY_DATA_LOCKED, - ssh_key_unlock='ASK', - created_by=self.user_sue, - ) - self.cred_holly = self.user_holly.credentials.create( - username='holly', - password='holly rocks', - created_by=self.user_sue, - ) - self.cred_iris = self.user_iris.credentials.create( - username='iris', - password='ASK', - created_by=self.user_sue, - ) - - # Each operations team also has shared credentials they can use. - self.cred_ops_east = self.team_ops_east.credentials.create( - username='east', - ssh_key_data=TEST_SSH_KEY_DATA_LOCKED, - ssh_key_unlock=TEST_SSH_KEY_DATA_UNLOCK, - created_by = self.user_sue, - ) - self.cred_ops_west = self.team_ops_west.credentials.create( - username='west', - password='Heading270', - created_by = self.user_sue, - ) - self.cred_ops_south = self.team_ops_south.credentials.create( - username='south', - password='Heading180', - created_by = self.user_sue, - ) - - self.cred_ops_north = self.team_ops_north.credentials.create( - username='north', - password='Heading0', - created_by = self.user_sue, - ) - - self.cred_ops_test = self.team_ops_testers.credentials.create( - username='testers', - password='HeadingNone', - created_by = self.user_sue, - ) - - self.ops_testers_permission = Permission.objects.create( - inventory = self.inv_ops_west, - project = self.proj_prod, - team = self.team_ops_testers, - permission_type = PERM_INVENTORY_CHECK, - created_by = self.user_sue - ) - - self.doug_check_permission = Permission.objects.create( - inventory = self.inv_eng, - project = self.proj_dev, - user = self.user_doug, - permission_type = PERM_INVENTORY_CHECK, - created_by = self.user_sue - ) - - self.juan_deploy_permission = Permission.objects.create( - inventory = self.inv_eng, - project = self.proj_dev, - user = self.user_juan, - permission_type = PERM_INVENTORY_DEPLOY, - created_by = self.user_sue - ) - - self.hannibal_create_permission = Permission.objects.create( - inventory = self.inv_eng, - project = self.proj_dev, - user = self.user_hannibal, - permission_type = PERM_JOBTEMPLATE_CREATE, - created_by = self.user_sue - ) - - # FIXME: Define explicit permissions for tests. - # other django user is on the project team and can deploy - #self.permission1 = Permission.objects.create( - # inventory = self.inventory, - # project = self.project, - # team = self.team, - # permission_type = PERM_INVENTORY_DEPLOY, - # created_by = self.normal_django_user - #) - # individual permission granted to other2 user, can run check mode - #self.permission2 = Permission.objects.create( - # inventory = self.inventory, - # project = self.project, - # user = self.other2_django_user, - # permission_type = PERM_INVENTORY_CHECK, - # created_by = self.normal_django_user - #) - - # Engineering has job templates to check/run the dev project onto - # their own inventory. - self.jt_eng_check = JobTemplate.objects.create( - name='eng-dev-check', - job_type='check', - inventory= self.inv_eng, - project=self.proj_dev, - playbook=self.proj_dev.playbooks[0], - host_config_key=uuid.uuid4().hex, - created_by=self.user_sue, - ) - # self.job_eng_check = self.jt_eng_check.create_job( - # created_by=self.user_sue, - # credential=self.cred_doug, - # ) - self.jt_eng_run = JobTemplate.objects.create( - name='eng-dev-run', - job_type='run', - inventory= self.inv_eng, - project=self.proj_dev, - playbook=self.proj_dev.playbooks[0], - host_config_key=uuid.uuid4().hex, - created_by=self.user_sue, - ) - # self.job_eng_run = self.jt_eng_run.create_job( - # created_by=self.user_sue, - # credential=self.cred_chuck, - # ) - - # Support has job templates to check/run the test project onto - # their own inventory. - self.jt_sup_check = JobTemplate.objects.create( - name='sup-test-check', - job_type='check', - inventory= self.inv_sup, - project=self.proj_test, - playbook=self.proj_test.playbooks[0], - host_config_key=uuid.uuid4().hex, - created_by=self.user_sue, - ) - # self.job_sup_check = self.jt_sup_check.create_job( - # created_by=self.user_sue, - # credential=self.cred_frank, - # ) - self.jt_sup_run = JobTemplate.objects.create( - name='sup-test-run', - job_type='run', - inventory= self.inv_sup, - project=self.proj_test, - playbook=self.proj_test.playbooks[0], - host_config_key=uuid.uuid4().hex, - credential=self.cred_eve, - created_by=self.user_sue, - ) - # self.job_sup_run = self.jt_sup_run.create_job( - # created_by=self.user_sue, - # ) - - # Operations has job templates to check/run the prod project onto - # both east and west inventories, by default using the team credential. - self.jt_ops_east_check = JobTemplate.objects.create( - name='ops-east-prod-check', - job_type='check', - inventory= self.inv_ops_east, - project=self.proj_prod, - playbook=self.proj_prod.playbooks[0], - credential=self.cred_ops_east, - host_config_key=uuid.uuid4().hex, - created_by=self.user_sue, - ) - # self.job_ops_east_check = self.jt_ops_east_check.create_job( - # created_by=self.user_sue, - # ) - self.jt_ops_east_run = JobTemplate.objects.create( - name='ops-east-prod-run', - job_type='run', - inventory= self.inv_ops_east, - project=self.proj_prod, - playbook=self.proj_prod.playbooks[0], - credential=self.cred_ops_east, - host_config_key=uuid.uuid4().hex, - created_by=self.user_sue, - ) - # self.job_ops_east_run = self.jt_ops_east_run.create_job( - # created_by=self.user_sue, - # ) - self.jt_ops_west_check = JobTemplate.objects.create( - name='ops-west-prod-check', - job_type='check', - inventory= self.inv_ops_west, - project=self.proj_prod, - playbook=self.proj_prod.playbooks[0], - credential=self.cred_ops_west, - host_config_key=uuid.uuid4().hex, - created_by=self.user_sue, - ) - # self.job_ops_west_check = self.jt_ops_west_check.create_job( - # created_by=self.user_sue, - # ) - self.jt_ops_west_run = JobTemplate.objects.create( - name='ops-west-prod-run', - job_type='run', - inventory= self.inv_ops_west, - project=self.proj_prod, - playbook=self.proj_prod.playbooks[0], - credential=self.cred_ops_west, - host_config_key=uuid.uuid4().hex, - created_by=self.user_sue, - ) - # self.job_ops_west_run = self.jt_ops_west_run.create_job( - # created_by=self.user_sue, - # ) - - def setUp(self): - super(BaseJobTestMixin, self).setUp() - self.start_redis() - self.setup_instances() - self.populate() - self.start_queue() - - def tearDown(self): - super(BaseJobTestMixin, self).tearDown() - self.stop_redis() - self.terminate_queue() - class JobTemplateTest(BaseJobTestMixin, django.test.TestCase): JOB_TEMPLATE_FIELDS = ('id', 'type', 'url', 'related', 'summary_fields', From 81205d37dcda3579a514b34e9a3691b070305fd3 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Wed, 18 Feb 2015 14:52:48 -0500 Subject: [PATCH 04/26] removed extra redudant import --- awx/main/tests/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/awx/main/tests/__init__.py b/awx/main/tests/__init__.py index d5b6667e43..7489dbd7b3 100644 --- a/awx/main/tests/__init__.py +++ b/awx/main/tests/__init__.py @@ -14,4 +14,3 @@ from awx.main.tests.activity_stream import * # noqa from awx.main.tests.schedules import * # noqa from awx.main.tests.redact import * # noqa from awx.main.tests.views import * # noqa -from awx.main.tests.jobs import * From 4204a479f30d90e1bff17025bcea7dac4ba29db5 Mon Sep 17 00:00:00 2001 From: Bill Nottingham Date: Wed, 18 Feb 2015 15:08:19 -0500 Subject: [PATCH 05/26] Fix some pip package names. --- awx/lib/site-packages/README | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/awx/lib/site-packages/README b/awx/lib/site-packages/README index 8176e221f0..741be9bf2c 100644 --- a/awx/lib/site-packages/README +++ b/awx/lib/site-packages/README @@ -21,7 +21,7 @@ django-celery==3.1.10 (djcelery/*) django-crum==0.6.1 (crum/*) django-extensions==1.3.3 (django_extensions/*) django-jsonfield==0.9.12 (jsonfield/*, minor fix in jsonfield/fields.py) -django-polymorphic==0.5.3 (polymorphic/*) +django_polymorphic==0.5.3 (polymorphic/*) django-split-settings==0.1.1 (split_settings/*) django-taggit==0.11.2 (taggit/*) djangorestframework==2.3.13 (rest_framework/*) @@ -36,9 +36,9 @@ kombu==3.0.21 (kombu/*) Markdown==2.4.1 (markdown/*, excluded bin/markdown_py) mock==1.0.1 (mock.py) ordereddict==1.1 (ordereddict.py, needed for Python 2.6 support) -os-diskconfig-python-novaclient-ext==0.1.2 (os_diskconfig_python_novaclient_ext/*) -os-networksv2-python-novaclient-ext==0.21 (os_networksv2_python_novaclient_ext.py) -os-virtual-interfacesv2-python-novaclient-ext==0.15 (os_virtual_interfacesv2_python_novaclient_ext.py) +os_diskconfig_python_novaclient_ext==0.1.2 (os_diskconfig_python_novaclient_ext/*) +os_networksv2_python_novaclient_ext==0.21 (os_networksv2_python_novaclient_ext.py) +os_virtual_interfacesv2_python_novaclient_ext==0.15 (os_virtual_interfacesv2_python_novaclient_ext.py) pbr==0.10.0 (pbr/*) pexpect==3.1 (pexpect/*, excluded pxssh.py, fdpexpect.py, FSM.py, screen.py, ANSI.py) @@ -51,8 +51,8 @@ python-swiftclient==2.2.0 (swiftclient/*, excluded bin/swift) pytz==2014.10 (pytz/*) rackspace-auth-openstack==1.3 (rackspace_auth_openstack/*) rackspace-novaclient==1.4 (no files) -rax-default-network-flags-python-novaclient-ext==0.2.3 (rax_default_network_flags_python_novaclient_ext/*) -rax-scheduled-images-python-novaclient-ext==0.2.1 (rax_scheduled_images_python_novaclient_ext/*) +rax_default_network_flags_python_novaclient_ext==0.2.3 (rax_default_network_flags_python_novaclient_ext/*) +rax_scheduled_images_python_novaclient_ext==0.2.1 (rax_scheduled_images_python_novaclient_ext/*) requests==2.5.1 (requests/*) setuptools==12.0.5 (setuptools/*, _markerlib/*, pkg_resources/*, easy_install.py) simplejson==3.6.0 (simplejson/*, excluded simplejson/_speedups.so) From 4f907c6645908ff61cd4964d6e897dc52fff8496 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Wed, 18 Feb 2015 15:30:51 -0500 Subject: [PATCH 06/26] make flake8 happy --- .../management/commands/run_callback_receiver.py | 1 - awx/main/management/commands/run_socketio_service.py | 1 - awx/main/management/commands/run_task_system.py | 1 - awx/main/models/jobs.py | 1 - awx/main/tests/base.py | 12 ++++++++---- awx/main/tests/jobs/__init__.py | 6 +++--- awx/main/tests/jobs/survey_password.py | 11 +++++------ awx/main/tests/tasks.py | 1 - awx/main/tests/views.py | 2 +- setup.cfg | 2 +- 10 files changed, 18 insertions(+), 20 deletions(-) mode change 100644 => 100755 setup.cfg diff --git a/awx/main/management/commands/run_callback_receiver.py b/awx/main/management/commands/run_callback_receiver.py index e883a1963c..3ce68147b3 100644 --- a/awx/main/management/commands/run_callback_receiver.py +++ b/awx/main/management/commands/run_callback_receiver.py @@ -15,7 +15,6 @@ from django.conf import settings from django.core.management.base import NoArgsCommand from django.db import transaction, DatabaseError from django.utils.dateparse import parse_datetime -from django.utils.timezone import now from django.utils.tzinfo import FixedOffset from django.db import connection diff --git a/awx/main/management/commands/run_socketio_service.py b/awx/main/management/commands/run_socketio_service.py index 2a47818893..787e181202 100644 --- a/awx/main/management/commands/run_socketio_service.py +++ b/awx/main/management/commands/run_socketio_service.py @@ -11,7 +11,6 @@ from threading import Thread # Django from django.conf import settings from django.core.management.base import NoArgsCommand -from django.utils.timezone import now # AWX import awx diff --git a/awx/main/management/commands/run_task_system.py b/awx/main/management/commands/run_task_system.py index 31acbb6af4..d7918e58f5 100644 --- a/awx/main/management/commands/run_task_system.py +++ b/awx/main/management/commands/run_task_system.py @@ -11,7 +11,6 @@ import time # Django from django.conf import settings from django.core.management.base import NoArgsCommand -from django.utils.timezone import now # AWX from awx.main.models import * # noqa diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 3e4a474c3d..dbdb8d5e51 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -5,7 +5,6 @@ import hmac import json import logging -import re # Django from django.conf import settings diff --git a/awx/main/tests/base.py b/awx/main/tests/base.py index 4da109718c..7261374c28 100644 --- a/awx/main/tests/base.py +++ b/awx/main/tests/base.py @@ -323,8 +323,8 @@ class BaseTestMixin(QueueTestMixin): job_template = self.make_job_template(created_by=created_by) opts = { - 'created_by': created_by, - 'status': inital_state, + 'created_by': created_by, + 'status': inital_state, } opts.update(kwargs) return job_template.create_job(**opts) @@ -448,9 +448,13 @@ class BaseTestMixin(QueueTestMixin): elif response['Content-Type'].startswith('application/yaml'): obj = yaml.safe_load(response.content) elif response['Content-Type'].startswith('text/plain'): - obj = { 'content': response.content } + obj = { + 'content': response.content + } elif response['Content-Type'].startswith('text/html'): - obj = { 'content': response.content } + obj = { + 'content': response.content + } else: self.fail('Unsupport response content type %s' % response['Content-Type']) else: diff --git a/awx/main/tests/jobs/__init__.py b/awx/main/tests/jobs/__init__.py index 73064040b6..7cba799343 100644 --- a/awx/main/tests/jobs/__init__.py +++ b/awx/main/tests/jobs/__init__.py @@ -1,3 +1,3 @@ -from jobs_monolithic import * -from survey_password import * -from base import * \ No newline at end of file +from awx.main.tests.jobs.jobs_monolithic import * # noqa +from survey_password import * # noqa +from base import * # noqa diff --git a/awx/main/tests/jobs/survey_password.py b/awx/main/tests/jobs/survey_password.py index 4d652675f2..60354da778 100644 --- a/awx/main/tests/jobs/survey_password.py +++ b/awx/main/tests/jobs/survey_password.py @@ -2,7 +2,6 @@ import json # Django -import django.test from django.core.urlresolvers import reverse # AWX @@ -91,32 +90,32 @@ TEST_SINGLE_PASSWORDS = [ { 'description': 'Single instance with a . after', 'text' : 'See spot. See spot run. See spot run %s. That is a fast run.' % PASSWORD, - 'passwords': [ PASSWORD ], + 'passwords': [PASSWORD], 'occurances': 1, }, { 'description': 'Single instance with , after', 'text': 'Spot goes %s, at a fast pace' % PASSWORD, - 'passwords': [ PASSWORD ], + 'passwords': [PASSWORD], 'occurances': 1, }, { 'description': 'Single instance with a space after', 'text': 'Is %s very fast?' % PASSWORD, - 'passwords': [ PASSWORD ], + 'passwords': [PASSWORD], 'occurances': 1, }, { 'description': 'Many instances, also with newline', 'text': 'I think %s is very very fast. If I ran %s for 4 hours how many hours would I run?.\nTrick question. %s for 4 hours would result in running for 4 hours' % (PASSWORD, PASSWORD, PASSWORD), - 'passwords': [ PASSWORD ], + 'passwords': [PASSWORD], 'occurances': 3, }, ] passwd = 'my!@#$%^pass&*()_+' TEST_SINGLE_PASSWORDS.append({ 'description': 'password includes characters not in a-z 0-9 range', - 'passwords': [ passwd ], + 'passwords': [passwd], 'text': 'Text is fun yeah with passwords %s.' % passwd, 'occurances': 1 }) diff --git a/awx/main/tests/tasks.py b/awx/main/tests/tasks.py index c0785597ba..d069f41744 100644 --- a/awx/main/tests/tasks.py +++ b/awx/main/tests/tasks.py @@ -13,7 +13,6 @@ import unittest # Django from django.conf import settings -from django.test.utils import override_settings from django.utils.timezone import now # Django-CRUM diff --git a/awx/main/tests/views.py b/awx/main/tests/views.py index 34924b1da8..629b1e1fbc 100644 --- a/awx/main/tests/views.py +++ b/awx/main/tests/views.py @@ -75,4 +75,4 @@ class UnifiedJobStdoutRedactedTests(BaseLiveServerTest, QueueStartStopTestMixin) self._test_redaction_enabled('html') def test_redaction_enabled_txt(self): - self._test_redaction_enabled('txt') \ No newline at end of file + self._test_redaction_enabled('txt') diff --git a/setup.cfg b/setup.cfg old mode 100644 new mode 100755 index b8dfcba657..bd64ca1943 --- a/setup.cfg +++ b/setup.cfg @@ -18,4 +18,4 @@ exclude=awx/lib/site-packages,awx/ui,awx/api/urls.py,awx/main/migrations,awx/mai [flake8] ignore=E201,E203,E221,E225,E231,E241,E251,E261,E265,E302,E303,E501,W291,W391,W293,E731 -exclude=awx/lib/site-packages,awx/ui,awx/api/urls.py,awx/main/migrations,awx/main/tests/data +exclude=awx/lib/site-packages,awx/ui,awx/api/urls.py,awx/main/migrations,awx/main/tests/data,node_modules/,awx/projects/ From a0ffd50207cfdf2de2c20d23d5d55f87a7b66e78 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Wed, 18 Feb 2015 16:00:08 -0500 Subject: [PATCH 07/26] Adjust error messages for survey per Chris' request to make them more accurate. --- awx/ui/static/js/forms/SurveyQuestion.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/awx/ui/static/js/forms/SurveyQuestion.js b/awx/ui/static/js/forms/SurveyQuestion.js index fe50a0a236..e6717897b0 100644 --- a/awx/ui/static/js/forms/SurveyQuestion.js +++ b/awx/ui/static/js/forms/SurveyQuestion.js @@ -106,13 +106,13 @@ export default control:'
'+ '
'+ ''+ - '
The minimum length you entered is not a number. Please enter a number.
'+ + '
The minimum length you entered is not a valid number. Please enter a whole number.
'+ '
The minimium length is too high. Please enter a lower number.
'+ '
The minimum length is too low. Please enter a positive number.
'+ '
'+ '
'+ ''+ - '
The maximum length you entered is not a number. Please enter a number.
'+ + '
The maximum length you entered is not a valid number. Please enter a whole nnumber.
'+ '
The maximum length is too low. Please enter a number larger than the minimum length you set.
'+ '
'+ '
', @@ -127,13 +127,13 @@ export default control:'
'+ '
'+ ''+ - '
The minimum length you entered is not a number. Please enter a number.
'+ + '
The minimum length you entered is not a valid number. Please enter a whole number.
'+ '
The minimium length is too high. Please enter a lower number.
'+ '
The minimum length is too low. Please enter a positive number.
'+ '
'+ '
'+ ''+ - '
The maximum length you entered is not a number. Please enter a number.
'+ + '
The maximum length you entered is not a valid number. Please enter a whole number.
'+ '
The maximum length is too low. Please enter a number larger than the minimum length you set.
'+ '
'+ '
', @@ -148,13 +148,13 @@ export default control:'
'+ '
'+ ''+ - '
The minimum length you entered is not a number. Please enter a number.
'+ + '
The minimum length you entered is not a valid number. Please enter a whole number.
'+ '
The minimium length is too high. Please enter a lower number.
'+ '
The minimum length is too low. Please enter a positive number.
'+ '
'+ '
'+ ''+ - '
The maximum length you entered is not a number. Please enter a number.
'+ + '
The maximum length you entered is not a valid number. Please enter a whole number.
'+ '
The maximum length is too low. Please enter a number larger than the minimum length you set.
'+ '
'+ '
', From 9698b79f3fee8d4a53defa728df1c6c45afa2996 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Wed, 18 Feb 2015 16:14:30 -0500 Subject: [PATCH 08/26] fix of the teams list console errors --- awx/ui/static/js/controllers/Teams.js | 34 ++++++++++++++++----------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/awx/ui/static/js/controllers/Teams.js b/awx/ui/static/js/controllers/Teams.js index 1ee97e649b..fccae68a36 100644 --- a/awx/ui/static/js/controllers/Teams.js +++ b/awx/ui/static/js/controllers/Teams.js @@ -202,19 +202,25 @@ export function TeamsEdit($scope, $rootScope, $compile, $location, $log, $routeP } $scope.teamLoadedRemove = $scope.$on('teamLoaded', function () { CheckAccess({ scope: $scope }); - Rest.setUrl($scope.organization_url); - Rest.get() - .success(function (data) { - $scope.organization_name = data.name; - master.organization_name = data.name; - Wait('stop'); - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve organization: ' + - $scope.orgnization_url + '. GET status: ' + status }); - }); - for (var set in relatedSets) { - $scope.search(relatedSets[set].iterator); + if ($scope.organization_url) { + Rest.setUrl($scope.organization_url); + Rest.get() + .success(function (data) { + $scope.organization_name = data.name; + master.organization_name = data.name; + Wait('stop'); + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve organization: ' + + $scope.orgnization_url + '. GET status: ' + status }); + }); + for (var set in relatedSets) { + $scope.search(relatedSets[set].iterator); + } + } else { + $scope.organization_name = ""; + master.organization_name = ""; + Wait('stop'); } }); @@ -387,4 +393,4 @@ export function TeamsEdit($scope, $rootScope, $compile, $location, $log, $routeP TeamsEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'TeamForm', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'RelatedSearchInit', 'RelatedPaginateInit', 'ReturnToCaller', 'ClearScope', 'LookUpInit', 'Prompt', 'GetBasePath', 'CheckAccess', 'OrganizationList', 'Wait', 'Stream' -]; \ No newline at end of file +]; From 78e1698333e78aa17e6aff62a59c7f0855428567 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Thu, 19 Feb 2015 09:20:05 -0500 Subject: [PATCH 09/26] redact survey password fields in job detail extra_vars --- awx/api/serializers.py | 8 ++++ awx/main/models/jobs.py | 17 +++++---- awx/main/tests/jobs/survey_password.py | 53 +++++++++++++++++++++----- 3 files changed, 62 insertions(+), 16 deletions(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 9078d2c46d..cc23e13a8e 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -33,6 +33,7 @@ from polymorphic import PolymorphicModel from awx.main.constants import SCHEDULEABLE_PROVIDERS from awx.main.models import * # noqa from awx.main.utils import get_type_for_model, get_model_for_type +from awx.main.redact import REPLACE_STR logger = logging.getLogger('awx.api.serializers') @@ -1419,6 +1420,13 @@ class JobSerializer(UnifiedJobSerializer, JobOptionsSerializer): return ret if 'job_template' in ret and (not obj.job_template or not obj.job_template.active): ret['job_template'] = None + + if obj.job_template and obj.job_template.survey_enabled: + extra_vars = json.loads(ret['extra_vars']) + for key in obj.job_template.survey_password_variables(): + if key in extra_vars: + extra_vars[key] = REPLACE_STR + ret['extra_vars'] = json.dumps(extra_vars) return ret diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index dbdb8d5e51..519aebeb77 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -209,6 +209,15 @@ class JobTemplate(UnifiedJobTemplate, JobOptions): vars.append(survey_element['variable']) return vars + def survey_password_variables(self): + vars = [] + if self.survey_enabled and 'spec' in self.survey_spec: + # Get variables that are type password + for survey_element in self.survey_spec['spec']: + if survey_element['type'] == 'password': + vars.append(survey_element['variable']) + return vars + def survey_variable_validation(self, data): errors = [] if not self.survey_enabled: @@ -458,14 +467,8 @@ class Job(UnifiedJob, JobOptions): # Then lookup password fields in extra_vars and save the values jt = self.job_template if jt and jt.survey_enabled and 'spec' in jt.survey_spec: - vars = [] - # Get variables that are type password - for survey_element in jt.survey_spec['spec']: - if survey_element['type'] == 'password': - vars.append(survey_element['variable']) - # Use password vars to find in extra_vars - for key in vars: + for key in jt.survey_password_variables(): if key in self.extra_vars_dict: content = PlainTextCleaner.remove_sensitive(content, self.extra_vars_dict[key]) return content diff --git a/awx/main/tests/jobs/survey_password.py b/awx/main/tests/jobs/survey_password.py index 60354da778..74b7d67ae5 100644 --- a/awx/main/tests/jobs/survey_password.py +++ b/awx/main/tests/jobs/survey_password.py @@ -8,7 +8,7 @@ from django.core.urlresolvers import reverse from awx.main.models import * # noqa from awx.main.tests.base import BaseTest -__all__ = ['SurveyPasswordTest'] +__all__ = ['SurveyPasswordRedactedTest'] PASSWORD="5m/h" ENCRYPTED_STR='$encrypted$' @@ -154,11 +154,41 @@ class SurveyPasswordBaseTest(BaseTest): self.check_found(response['content'], ENCRYPTED_STR, test['occurances'], test['description']) - def _get_url_job_stdout(self, job): - job_stdout_url = reverse('api:job_stdout', args=(job.pk,)) - return self.get(job_stdout_url, expect=200, auth=self.get_super_credentials(), accept='application/json') + # TODO: A more complete test would ensure that the variable value isn't found + def check_extra_vars_redacted(self, test, response): + self.assertIsNotNone(response) + # Ensure that all extra_vars of type password have the value '$encrypted$' + vars = [] + for question in test['survey']['spec']: + if question['type'] == 'password': + vars.append(question['variable']) -class SurveyPasswordTest(SurveyPasswordBaseTest): + extra_vars = json.loads(response['extra_vars']) + for var in vars: + self.assertIn(var, extra_vars, 'Variable "%s" should exist in "%s"' % (var, extra_vars)) + self.assertEqual(extra_vars[var], ENCRYPTED_STR) + + def _get_url_job_stdout(self, job): + url = reverse('api:job_stdout', args=(job.pk,)) + return self.get(url, expect=200, auth=self.get_super_credentials(), accept='application/json') + + def _get_url_job_details(self, job): + url = reverse('api:job_detail', args=(job.pk,)) + return self.get(url, expect=200, auth=self.get_super_credentials(), accept='application/json') + +class SurveyPasswordRedactedTest(SurveyPasswordBaseTest): + ''' + Transpose TEST[]['tests'] to the below format. A more flat format." + [ + { + 'text': '...', + 'description': '...', + ..., + 'job': '...', + 'survey': '...' + }, + ] + ''' def setup_test(self, test_name): blueprint = TESTS[test_name] self.tests[test_name] = [] @@ -178,25 +208,30 @@ class SurveyPasswordTest(SurveyPasswordBaseTest): job.result_stdout_text = test['text'] job.save() test['job'] = job + test['survey'] = blueprint['survey'] self.tests[test_name].append(test) def setUp(self): - super(SurveyPasswordTest, self).setUp() + super(SurveyPasswordRedactedTest, self).setUp() self.tests = {} self.setup_test('simple') self.setup_test('complex') # should redact single variable survey - def test_survey_password_redact_simple_survey(self): + def test_redact_stdout_simple_survey(self): for test in self.tests['simple']: response = self._get_url_job_stdout(test['job']) self.check_passwords_redacted(test, response) # should redact multiple variables survey - def test_survey_password_redact_complex_survey(self): + def test_redact_stdout_complex_survey(self): for test in self.tests['complex']: response = self._get_url_job_stdout(test['job']) self.check_passwords_redacted(test, response) - \ No newline at end of file + # should redact values in extra_vars + def test_redact_job_extra_vars(self): + for test in self.tests['simple']: + response = self._get_url_job_details(test['job']) + self.check_extra_vars_redacted(test, response) From ea8a537906d954b90962e579e7cdc5889d328b85 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Thu, 19 Feb 2015 11:04:39 -0500 Subject: [PATCH 10/26] Job Submission extra vars When submitting a job to launch, the UI will collect extra_vars from the the JT. However with a recent change to make extra_vars include comments, the UI no longer receives the JT extra vars as a JSON obj, but rather as a yaml-unparsed string. So I am changing the way we parse these JT-extra_vars from 'json' to 'yaml' since we no longer expect to recieve a json-ified extra var object. --- awx/ui/static/js/helpers/JobSubmission.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/static/js/helpers/JobSubmission.js b/awx/ui/static/js/helpers/JobSubmission.js index cb0d3b8be6..faab6dfc6d 100644 --- a/awx/ui/static/js/helpers/JobSubmission.js +++ b/awx/ui/static/js/helpers/JobSubmission.js @@ -39,7 +39,7 @@ angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'Credential Rest.get() .success(function (data) { if(!Empty(data.extra_vars)){ - data.extra_vars = ToJSON('json', data.extra_vars, false); + data.extra_vars = ToJSON('yaml', data.extra_vars, false); $.each(data.extra_vars, function(key,value){ job_launch_data.extra_vars[key] = value; }); From 0286800ead839d6f20b2a816d4c55529aebb184f Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Thu, 19 Feb 2015 12:44:39 -0500 Subject: [PATCH 11/26] Survey password field initialization --- awx/ui/static/js/helpers/Survey.js | 1 + 1 file changed, 1 insertion(+) diff --git a/awx/ui/static/js/helpers/Survey.js b/awx/ui/static/js/helpers/Survey.js index cc1834accf..ed7bae3787 100644 --- a/awx/ui/static/js/helpers/Survey.js +++ b/awx/ui/static/js/helpers/Survey.js @@ -756,6 +756,7 @@ angular.module('SurveyHelper', [ 'Utilities', 'RestServices', 'SchedulesHelper', scope.default_float = ""; scope.default_int = ""; scope.default_textarea = ""; + scope.default_password = "" ; scope.choices = ""; scope.text_min = ""; scope.text_max = "" ; From db8eed2ae577c25663a2b0a22b6d04a8d090e936 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Thu, 19 Feb 2015 12:59:09 -0500 Subject: [PATCH 12/26] Job submission for optional survey questions Make sure optional multiselect and multiplechoice answers are ignored if not answered --- awx/ui/static/js/helpers/JobSubmission.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awx/ui/static/js/helpers/JobSubmission.js b/awx/ui/static/js/helpers/JobSubmission.js index faab6dfc6d..c673bb3122 100644 --- a/awx/ui/static/js/helpers/JobSubmission.js +++ b/awx/ui/static/js/helpers/JobSubmission.js @@ -77,7 +77,7 @@ angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'Credential for (var i=0; i < scope.survey_questions.length; i++){ var fld = scope.survey_questions[i].variable; // grab all survey questions that have answers - if(scope[fld]) { + if(scope.survey_questions[i].required || (scope.survey_questions[i].required === false && scope[fld].toString()!=="")) { job_launch_data.extra_vars[fld] = scope[fld]; } // for optional text and text-areas, submit a blank string if min length is 0 @@ -544,7 +544,7 @@ angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'Credential } if(question.type === "textarea"){ - scope[question.variable] = question.default || question.default_textarea; + scope[question.variable] = (question.default_textarea) ? question.default_textarea : (question.default) ? question.default : ""; minlength = (!Empty(question.min)) ? Number(question.min) : ""; maxlength =(!Empty(question.max)) ? Number(question.max) : "" ; html+='