From 67ae8effa4adad7374cc75f851711dbb4378ed93 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Fri, 30 Aug 2013 03:08:24 -0400 Subject: [PATCH] AC-132 Added API support to prompt for SCM passwords when updating a project. --- awx/main/models/__init__.py | 24 +++++++++++---- awx/main/tasks.py | 2 +- awx/main/tests/projects.py | 59 +++++++++++++++++++++++++++++-------- awx/main/views.py | 8 ++--- 4 files changed, 70 insertions(+), 23 deletions(-) diff --git a/awx/main/models/__init__.py b/awx/main/models/__init__.py index 0d94025af6..3b9db1f99c 100644 --- a/awx/main/models/__init__.py +++ b/awx/main/models/__init__.py @@ -633,11 +633,13 @@ class Project(CommonModel): @property def needs_scm_password(self): - return not self.scm_key_data and self.ssh_password == 'ASK' + return self.scm_type and not self.scm_key_data and \ + self.scm_password == 'ASK' @property def needs_scm_key_unlock(self): - return 'ENCRYPTED' in self.scm_key_data and \ + return self.scm_type and self.scm_key_data and \ + 'ENCRYPTED' in self.scm_key_data and \ (not self.scm_key_unlock or self.scm_key_unlock == 'ASK') @property @@ -648,10 +650,14 @@ class Project(CommonModel): needed.append(field) return needed - def update(self): + def update(self, **kwargs): if self.scm_type: + needed = self.scm_passwords_needed + opts = dict([(field, kwargs.get(field, '')) for field in needed]) + if not all(opts.values()): + return project_update = self.project_updates.create() - project_update.start() + project_update.start(**opts) return project_update @property @@ -794,6 +800,10 @@ class ProjectUpdate(PrimordialModel): except TaskMeta.DoesNotExist: pass + def get_passwords_needed_to_start(self): + '''Return list of password field names needed to start the job.''' + return (self.credential and self.credential.passwords_needed) or [] + @property def can_start(self): return bool(self.status == 'new') @@ -802,9 +812,13 @@ class ProjectUpdate(PrimordialModel): from awx.main.tasks import RunProjectUpdate if not self.can_start: return False + needed = self.project.scm_passwords_needed + opts = dict([(field, kwargs.get(field, '')) for field in needed]) + if not all(opts.values()): + return False self.status = 'pending' self.save(update_fields=['status']) - task_result = RunProjectUpdate().delay(self.pk, **kwargs) + task_result = RunProjectUpdate().delay(self.pk, **opts) # Reload project update from database so we don't clobber results # from RunProjectUpdate (mainly from tests when using Django 1.4.x). project_update = ProjectUpdate.objects.get(pk=self.pk) diff --git a/awx/main/tasks.py b/awx/main/tasks.py index faadf12adb..3b56780e75 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -129,7 +129,7 @@ class BaseTask(Task): expect_passwords = {} for n, item in enumerate(self.get_password_prompts().items()): expect_list.append(item[0]) - expect_passwords[n] = passwords.get(item[1], '') + expect_passwords[n] = passwords.get(item[1], '') or '' expect_list.extend([pexpect.TIMEOUT, pexpect.EOF]) while child.isalive(): result_id = child.expect(expect_list, timeout=2) diff --git a/awx/main/tests/projects.py b/awx/main/tests/projects.py index 678c852ef3..01ed2e74b2 100644 --- a/awx/main/tests/projects.py +++ b/awx/main/tests/projects.py @@ -19,6 +19,7 @@ from django.test.utils import override_settings # AWX from awx.main.models import * from awx.main.tests.base import BaseTest, BaseTransactionTest +from awx.main.tests.tasks import TEST_SSH_KEY_DATA_LOCKED, TEST_SSH_KEY_DATA_UNLOCK TEST_PLAYBOOK = '''- hosts: mygroup gather_facts: false @@ -621,7 +622,6 @@ class ProjectUpdatesTest(BaseTransactionTest): def setUp(self): super(ProjectUpdatesTest, self).setUp() self.setup_users() - #self.skipTest('blah') def create_project(self, **kwargs): project = Project.objects.create(**kwargs) @@ -629,17 +629,10 @@ class ProjectUpdatesTest(BaseTransactionTest): self._temp_project_dirs.append(project_path) return project - def update_url_auth(self, url, username=None, password=None): - parts = urlparse.urlsplit(url) - - - def check_project_update(self, project, should_fail=False): - #print project.local_path - pu = project.update() + def check_project_update(self, project, should_fail=False, **kwargs): + pu = project.update(**kwargs) self.assertTrue(pu) pu = ProjectUpdate.objects.get(pk=pu.pk) - #print pu.status - #print pu.result_traceback if should_fail: self.assertEqual(pu.status, 'failed', pu.result_stdout) else: @@ -647,9 +640,6 @@ class ProjectUpdatesTest(BaseTransactionTest): project = Project.objects.get(pk=project.pk) self.assertEqual(project.last_update, pu) self.assertEqual(project.last_update_failed, pu.failed) - #print pu.result_traceback - #print pu.result_stdout - #print return pu def change_file_in_project(self, project): @@ -838,3 +828,46 @@ class ProjectUpdatesTest(BaseTransactionTest): scm_password=scm_password, ) self.check_project_scm(project) + + def test_prompt_for_scm_password_on_update(self): + scm_url = getattr(settings, 'TEST_GIT_PUBLIC_HTTPS', + 'https://github.com/ansible/ansible.github.com.git') + if not all([scm_url]): + self.skipTest('no public git repo defined for https!') + project = self.create_project( + name='my public git project over https', + scm_type='git', + scm_url=scm_url, + scm_username='nobody', + scm_password='ASK', + ) + url = reverse('main:project_update_view', args=(project.pk,)) + with self.current_user(self.super_django_user): + response = self.get(url, expect=200) + self.assertTrue(response['can_update']) + self.assertTrue('scm_password' in response['passwords_needed_to_update']) + with self.current_user(self.super_django_user): + response = self.post(url, {}, expect=400) + self.assertTrue('scm_password' in response['passwords_needed_to_update']) + with self.current_user(self.super_django_user): + response = self.post(url, {'scm_password': 'blah'}, expect=202) + + def test_prompt_for_scm_key_unlock_on_update(self): + scm_url = 'git@github.com:ansible/ansible.github.com.git' + project = self.create_project( + name='my public git project over https', + scm_type='git', + scm_url=scm_url, + scm_key_data=TEST_SSH_KEY_DATA_LOCKED, + scm_key_unlock='ASK', + ) + url = reverse('main:project_update_view', args=(project.pk,)) + with self.current_user(self.super_django_user): + response = self.get(url, expect=200) + self.assertTrue(response['can_update']) + self.assertTrue('scm_key_unlock' in response['passwords_needed_to_update']) + with self.current_user(self.super_django_user): + response = self.post(url, {}, expect=400) + self.assertTrue('scm_key_unlock' in response['passwords_needed_to_update']) + with self.current_user(self.super_django_user): + response = self.post(url, {'scm_key_unlock': TEST_SSH_KEY_DATA_UNLOCK}, expect=202) diff --git a/awx/main/views.py b/awx/main/views.py index 3c88d22953..92c78447e6 100644 --- a/awx/main/views.py +++ b/awx/main/views.py @@ -270,16 +270,16 @@ class ProjectUpdateView(GenericAPIView): data = dict( can_update=bool(obj.scm_type), ) - #if obj.scm_type: - # data['passwords_needed_to_update'] = obj.get_passwords_needed_to_start() + if obj.scm_type: + data['passwords_needed_to_update'] = obj.scm_passwords_needed return Response(data) def post(self, request, *args, **kwargs): obj = self.get_object() if bool(obj.scm_type): - project_update = obj.update() + project_update = obj.update(**request.DATA) if not project_update: - data = dict(msg='Unable to update project!') + data = dict(passwords_needed_to_update=obj.scm_passwords_needed) return Response(data, status=status.HTTP_400_BAD_REQUEST) else: return Response(status=status.HTTP_202_ACCEPTED)