AC-626 Removed support for prompting for password and ssh_key_unlock for scm/cloud credentials.

This commit is contained in:
Chris Church 2013-11-16 17:59:51 -05:00
parent f254f8bc92
commit 621cbb9f66
9 changed files with 48 additions and 144 deletions

View File

@ -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" %}

View File

@ -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.

View File

@ -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" %}

View File

@ -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)

View File

@ -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):

View File

@ -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)

View File

@ -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:

View File

@ -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):

View File

@ -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)