diff --git a/awx/api/templates/api/inventory_source_update_view.md b/awx/api/templates/api/inventory_source_update_view.md index e88eede86c..1d71a794b7 100644 --- a/awx/api/templates/api/inventory_source_update_view.md +++ b/awx/api/templates/api/inventory_source_update_view.md @@ -1,18 +1,13 @@ # Update Inventory Source Make a GET request to this resource to determine if the group can be updated -from its inventory source and whether any passwords are required for the -update. The response will include the following fields: +from its inventory source. The response will include the following field: -* `can_start`: Flag indicating if this job can be started (boolean, read-only) -* `passwords_needed_to_update`: Password names required to update from the - inventory source (array, read-only) +* `can_update`: Flag indicating if this inventory source can be updated + (boolean, read-only) -Make a POST request to this resource to update the inventory source. If any -passwords are required, they must be passed via POST data. - -If successful, the response status code will be 202. If any required passwords -are not provided, a 400 status code will be returned. If the inventory source -is not defined or cannot be updated, a 405 status code will be returned. +Make a POST request to this resource to update the inventory source. If +successful, the response status code will be 202. If the inventory source is +not defined or cannot be updated, a 405 status code will be returned. {% include "api/_new_in_awx.md" %} diff --git a/awx/api/templates/api/job_start.md b/awx/api/templates/api/job_start.md index 91c8f2d9d5..4b15ebc76b 100644 --- a/awx/api/templates/api/job_start.md +++ b/awx/api/templates/api/job_start.md @@ -5,7 +5,8 @@ whether any passwords are required to start the job. The response will include the following fields: * `can_start`: Flag indicating if this job can be started (boolean, read-only) -* `passwords_needed_to_start`: Password names required to start the job (array, read-only) +* `passwords_needed_to_start`: Password names required to start the job (array, + read-only) Make a POST request to this resource to start the job. If any passwords are required, they must be passed via POST data. diff --git a/awx/api/templates/api/project_update_view.md b/awx/api/templates/api/project_update_view.md index 08a5900f2a..086c6e6ee1 100644 --- a/awx/api/templates/api/project_update_view.md +++ b/awx/api/templates/api/project_update_view.md @@ -1,19 +1,12 @@ # Update Project Make a GET request to this resource to determine if the project can be updated -from its SCM source and whether any passwords are required for the update. The -response will include the following fields: +from its SCM source. The response will include the following field: * `can_update`: Flag indicating if this project can be updated (boolean, read-only) -* `passwords_needed_to_update`: Password names required to update the project - (array, read-only) -Make a POST request to this resource to update the project. If any passwords -are required, they must be passed via POST data. - -If successful, the response status code will be 202. If any required passwords -are not provided, a 400 status code will be returned. If the project cannot be -updated, a 405 status code will be returned. +Make a POST request to this resource to update the project. If the project +cannot be updated, a 405 status code will be returned. {% include "api/_new_in_awx.md" %} diff --git a/awx/api/views.py b/awx/api/views.py index 05a42d512b..9e7951ecb3 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -299,8 +299,6 @@ class ProjectUpdateView(GenericAPIView): data = dict( can_update=obj.can_update, ) - if obj.scm_type: - data['passwords_needed_to_update'] = obj.scm_passwords_needed return Response(data) def post(self, request, *args, **kwargs): @@ -308,8 +306,7 @@ class ProjectUpdateView(GenericAPIView): if obj.can_update: project_update = obj.update(**request.DATA) if not project_update: - data = dict(passwords_needed_to_update=obj.scm_passwords_needed) - return Response(data, status=status.HTTP_400_BAD_REQUEST) + return Response({}, status=status.HTTP_400_BAD_REQUEST) else: headers = {'Location': project_update.get_absolute_url()} return Response(status=status.HTTP_202_ACCEPTED, headers=headers) diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 1732e5cece..a5c5517b27 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -143,8 +143,6 @@ class JobTemplate(CommonModel): needed.append('ssh_password') else: needed.append(pw) - if self.project.scm_update_on_launch: - needed.extend(self.project.scm_passwords_needed) return bool(self.credential and not len(needed)) class Job(CommonTask): @@ -262,8 +260,6 @@ class Job(CommonTask): needed.append('ssh_password') else: needed.append(pw) - if self.project.scm_update_on_launch: - needed.extend(self.project.scm_passwords_needed) return needed def _get_task_class(self): diff --git a/awx/main/models/organization.py b/awx/main/models/organization.py index b4442a8452..6d80c080fc 100644 --- a/awx/main/models/organization.py +++ b/awx/main/models/organization.py @@ -200,7 +200,8 @@ class Credential(CommonModelNameNotUnique): default='', max_length=1024, verbose_name=_('Password'), - help_text=_('Password for this credential.'), + help_text=_('Password for this credential (or "ASK" to prompt the ' + 'user for machine credentials).'), ) ssh_key_data = models.TextField( blank=True, @@ -214,7 +215,7 @@ class Credential(CommonModelNameNotUnique): default='', verbose_name=_('SSH key unlock'), help_text=_('Passphrase to unlock SSH private key if encrypted (or ' - '"ASK" to prompt the user).'), + '"ASK" to prompt the user for machine credentials).'), ) sudo_username = models.CharField( max_length=1024, @@ -231,16 +232,16 @@ class Credential(CommonModelNameNotUnique): @property def needs_password(self): - return not self.ssh_key_data and self.password == 'ASK' + return self.kind == 'ssh' and self.password == 'ASK' @property def needs_ssh_key_unlock(self): - return 'ENCRYPTED' in decrypt_field(self, 'ssh_key_data') and \ - (not self.ssh_key_unlock or self.ssh_key_unlock == 'ASK') + return self.kind == 'ssh' and self.ssh_key_unlock == 'ASK' and \ + 'ENCRYPTED' in decrypt_field(self, 'ssh_key_data') @property def needs_sudo_password(self): - return self.sudo_password == 'ASK' + return self.kind == 'ssh' and self.sudo_password == 'ASK' @property def passwords_needed(self): @@ -321,7 +322,8 @@ class Credential(CommonModelNameNotUnique): # If update_fields has been specified, add our field names to it, # if hit hasn't been specified, then we're just doing a normal save. for field in self.PASSWORD_FIELDS: - encrypted = encrypt_field(self, field, bool(field != 'ssh_key_data')) + ask = bool(self.kind == 'ssh' and field != 'ssh_key_data') + encrypted = encrypt_field(self, field, ask) setattr(self, field, encrypted) if field not in update_fields: update_fields.append(field) diff --git a/awx/main/models/projects.py b/awx/main/models/projects.py index 05034f3990..8a664985ec 100644 --- a/awx/main/models/projects.py +++ b/awx/main/models/projects.py @@ -186,27 +186,10 @@ class Project(CommonModel): update_fields.append('local_path') if update_fields: self.save(update_fields=update_fields) - # If we just created a new project with SCM and it doesn't require any - # passwords to update, start the initial update. - if new_instance and self.scm_type and not self.scm_passwords_needed: + # If we just created a new project with SCM, start the initial update. + if new_instance and self.scm_type: self.update() - @property - def needs_scm_password(self): - return self.credential and self.credential.needs_password - - @property - def needs_scm_key_unlock(self): - return self.credential and self.credential.needs_ssh_key_unlock - - @property - def scm_passwords_needed(self): - needed = [] - for field in ('scm_password', 'scm_key_unlock'): - if getattr(self, 'needs_%s' % field): - needed.append(field) - return needed - def set_status_and_last_updated(self, save=True): # Determine current status. if self.scm_type: @@ -256,12 +239,8 @@ class Project(CommonModel): def update(self, **kwargs): if self.can_update: - 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(**opts) + project_update.start() return project_update def get_absolute_url(self): @@ -332,9 +311,6 @@ class ProjectUpdate(CommonTask): from awx.main.tasks import RunProjectUpdate return RunProjectUpdate - def _get_passwords_needed_to_start(self): - return self.project.scm_passwords_needed - def _update_parent_instance(self): parent_instance = self._get_parent_instance() if parent_instance: diff --git a/awx/main/tasks.py b/awx/main/tasks.py index edfb8e848e..b70a176b9d 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -440,9 +440,7 @@ class RunJob(BaseTask): try: project_update = pu_qs[0] except IndexError: - kw = dict([(k,v) for k,v in kwargs.items() - if k.startswith('scm_')]) - project_update = project.update(**kw) + project_update = project.update() if not project_update: msg = 'Unable to update project before launch.' job = self.update_model(pk, status='error', @@ -460,10 +458,7 @@ class RunJob(BaseTask): try: inventory_update = iu_qs.filter(inventory_source=inventory_source)[0] except IndexError: - # FIXME: Doesn't support multiple sources!!! - kw = dict([(k,v) for k,v in kwargs.items() - if k.startswith('source_')]) - inventory_update = inventory_source.update(**kw) + inventory_update = inventory_source.update() if not inventory_update: msgs.append('Unable to update inventory source %d before launch' % inventory_source.pk) continue @@ -528,12 +523,12 @@ class RunProjectUpdate(BaseTask): **kwargs) project = project_update.project if project.credential: - value = kwargs.get('scm_key_unlock', decrypt_field(project.credential, 'ssh_key_unlock')) + value = decrypt_field(project.credential, 'ssh_key_unlock') if value not in ('', 'ASK'): passwords['scm_key_unlock'] = value passwords['scm_username'] = project.credential.username - passwords['scm_password'] = kwargs.get('scm_password', - decrypt_field(project.credential, 'password')) + passwords['scm_password'] = decrypt_field(project.credential, + 'password') return passwords def build_env(self, project_update, **kwargs): diff --git a/awx/main/tests/projects.py b/awx/main/tests/projects.py index dc1f28ce49..b70f43ece7 100644 --- a/awx/main/tests/projects.py +++ b/awx/main/tests/projects.py @@ -918,7 +918,7 @@ class ProjectUpdatesTest(BaseTransactionTest): scm_password = kwargs.get('scm_password', decrypt_field(project.credential, 'password')) - if scm_password not in ('', 'ASK'): + if scm_password: self.assertFalse(scm_password in pu.job_args, pu.job_args) self.assertFalse(scm_password in json.dumps(pu.job_env), json.dumps(pu.job_env)) @@ -932,7 +932,7 @@ class ProjectUpdatesTest(BaseTransactionTest): scm_key_unlock = kwargs.get('scm_key_unlock', decrypt_field(project.credential, 'ssh_key_unlock')) - if scm_key_unlock not in ('', 'ASK'): + if scm_key_unlock: self.assertFalse(scm_key_unlock in pu.job_args, pu.job_args) self.assertFalse(scm_key_unlock in json.dumps(pu.job_env), json.dumps(pu.job_env)) @@ -968,7 +968,7 @@ class ProjectUpdatesTest(BaseTransactionTest): project_path = project.get_project_path(check_if_exists=False) # If project could be auto-updated on creation, the project dir should # already exist, otherwise run an initial checkout. - if project.scm_type and not project.scm_passwords_needed: + if project.scm_type: self.assertTrue(project.last_update) self.check_project_update(project, project_udpate=project.last_update) @@ -1035,7 +1035,8 @@ class ProjectUpdatesTest(BaseTransactionTest): # Change username/password for private projects and verify the update # fails (but doesn't cause the task to hang). scm_url_parts = urlparse.urlsplit(project.scm_url) - if 0 and project.scm_username and project.scm_password not in ('', 'ASK'): + # FIXME: Implement these tests again with new credentials! + if 0 and project.scm_username and project.scm_password: scm_username = project.scm_username should_still_fail = not (getpass.getuser() == scm_username and scm_url_parts.hostname == 'localhost' and @@ -1344,59 +1345,6 @@ class ProjectUpdatesTest(BaseTransactionTest): ) 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('api: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': 'blah1234'}, expect=202) - project_update = project.project_updates.order_by('-pk')[0] - self.check_project_update(project, should_fail=False, - scm_password='blah1234', - project_update=project_update) - - 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 ssh', - scm_type='git', - scm_url=scm_url, - scm_key_data=TEST_SSH_KEY_DATA_LOCKED, - scm_key_unlock='ASK', - ) - url = reverse('api: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) - project_update = project.project_updates.order_by('-pk')[0] - self.check_project_update(project, should_fail=None, - scm_key_unlock=TEST_SSH_KEY_DATA_UNLOCK, - project_update=project_update) - # Verify that we responded to ssh-agent prompt. - self.assertTrue('Identity added' in project_update.result_stdout) - def create_test_job_template(self, **kwargs): opts = { 'name': 'test-job-template %s' % str(now()), @@ -1491,33 +1439,34 @@ class ProjectUpdatesTest(BaseTransactionTest): scm_type='git', scm_url=scm_url, scm_username=scm_username, - scm_password='ASK', + scm_password=scm_password, scm_update_on_launch=True, ) - self.check_project_update(self.project, scm_password=scm_password) - self.assertEqual(self.project.project_updates.count(), 1) + self.check_project_update(self.project) + self.assertEqual(self.project.project_updates.count(), 2) job_template = self.create_test_job_template() job = self.create_test_job(job_template=job_template) self.assertEqual(job.status, 'new') - self.assertTrue(job.passwords_needed_to_start) - self.assertTrue('scm_password' in job.passwords_needed_to_start) - self.assertTrue(job.start(**{'scm_password': scm_password})) + self.assertFalse(job.passwords_needed_to_start) + self.assertTrue(job.start()) self.assertEqual(job.status, 'pending') job = Job.objects.get(pk=job.pk) self.assertTrue(job.status in ('successful', 'failed'), job.result_stdout + job.result_traceback) - self.assertEqual(self.project.project_updates.count(), 2) - # Try again but with a bad password - the job should flag an error - # because the project update failed. + self.assertEqual(self.project.project_updates.count(), 3) + # Try again but set a bad project password - the job should flag an + # error because the project update failed. + cred = self.project.credential + cred.password = 'bad scm password' + cred.save() job = self.create_test_job(job_template=job_template) self.assertEqual(job.status, 'new') - self.assertTrue(job.passwords_needed_to_start) - self.assertTrue('scm_password' in job.passwords_needed_to_start) - self.assertTrue(job.start(**{'scm_password': 'lasdkfjlsdkfj'})) + self.assertFalse(job.passwords_needed_to_start) + self.assertTrue(job.start()) self.assertEqual(job.status, 'pending') job = Job.objects.get(pk=job.pk) # FIXME: Not quite sure why the project update still returns successful # in this case? #self.assertEqual(job.status, 'error', # '\n'.join([job.result_stdout, job.result_traceback])) - self.assertEqual(self.project.project_updates.count(), 3) + self.assertEqual(self.project.project_updates.count(), 4)