mirror of
https://github.com/ansible/awx.git
synced 2026-01-13 11:00:03 -03:30
AC-626 Removed support for prompting for password and ssh_key_unlock for scm/cloud credentials.
This commit is contained in:
parent
f254f8bc92
commit
621cbb9f66
@ -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" %}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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" %}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user