AC-132 More updates after testing various SCM username/password/key options.

This commit is contained in:
Chris Church
2013-09-07 19:38:27 -04:00
parent c1e8b3c842
commit 663dfa24a2
3 changed files with 73 additions and 17 deletions

View File

@@ -7,6 +7,7 @@ import distutils.version
import json import json
import logging import logging
import os import os
import re
import subprocess import subprocess
import tempfile import tempfile
import time import time
@@ -64,8 +65,10 @@ class BaseTask(Task):
Create a temporary file containing the SSH private key. Create a temporary file containing the SSH private key.
''' '''
ssh_key_data = '' ssh_key_data = ''
if hasattr(instance, 'scm_key_data'): if hasattr(instance, 'project'):
ssh_key_data = decrypt_field(instance, 'scm_key_data') project = instance.project
if hasattr(project, 'scm_key_data'):
ssh_key_data = decrypt_field(project, 'scm_key_data')
elif hasattr(instance, 'credential'): elif hasattr(instance, 'credential'):
credential = instance.credential credential = instance.credential
if hasattr(credential, 'ssh_key_data'): if hasattr(credential, 'ssh_key_data'):
@@ -82,9 +85,13 @@ class BaseTask(Task):
def build_passwords(self, instance, **kwargs): def build_passwords(self, instance, **kwargs):
''' '''
Build a dictionary of passwords responding to prompts. Build a dictionary of passwords for responding to prompts.
''' '''
return {} return {
'yes': 'yes',
'no': 'no',
'': '',
}
def build_env(self, instance, **kwargs): def build_env(self, instance, **kwargs):
''' '''
@@ -222,7 +229,7 @@ class RunJob(BaseTask):
''' '''
Build a dictionary of passwords for SSH private key, SSH user and sudo. Build a dictionary of passwords for SSH private key, SSH user and sudo.
''' '''
passwords = {} passwords = super(RunJob, self).build_passwords(job, **kwargs)
creds = job.credential creds = job.credential
if creds: if creds:
for field in ('ssh_key_unlock', 'ssh_password', 'sudo_password'): for field in ('ssh_key_unlock', 'ssh_password', 'sudo_password'):
@@ -339,13 +346,15 @@ class RunProjectUpdate(BaseTask):
name = 'run_project_update' name = 'run_project_update'
model = ProjectUpdate model = ProjectUpdate
idle_timeout = 30 #idle_timeout = 30
def build_passwords(self, project_update, **kwargs): def build_passwords(self, project_update, **kwargs):
''' '''
Build a dictionary of passwords for SSH private key. Build a dictionary of passwords for SSH private key unlock and SCM
username/password.
''' '''
passwords = {} passwords = super(RunProjectUpdate, self).build_passwords(project_update,
**kwargs)
project = project_update.project project = project_update.project
value = decrypt_field(project, 'scm_key_unlock') value = decrypt_field(project, 'scm_key_unlock')
if value not in ('', 'ASK'): if value not in ('', 'ASK'):
@@ -360,6 +369,7 @@ class RunProjectUpdate(BaseTask):
''' '''
env = super(RunProjectUpdate, self).build_env(project_update, **kwargs) env = super(RunProjectUpdate, self).build_env(project_update, **kwargs)
env['ANSIBLE_ASK_SUDO_PASS'] = str(False) env['ANSIBLE_ASK_SUDO_PASS'] = str(False)
env['DISPLAY'] = '' # Prevent stupid password popup when running tests.
return env return env
def update_url_auth(self, url, username=None, password=None): def update_url_auth(self, url, username=None, password=None):
@@ -384,24 +394,32 @@ class RunProjectUpdate(BaseTask):
# Since we specify -vvv and tasks use async polling, we should get some # Since we specify -vvv and tasks use async polling, we should get some
# output regularly... # output regularly...
args.append('-%s' % ('v' * 3)) args.append('-%s' % ('v' * 3))
extra_vars = {}
project = project_update.project project = project_update.project
scm_url = project.scm_url scm_url = project.scm_url
if project.scm_username and project.scm_password not in ('ASK', ''): if project.scm_username and project.scm_password not in ('ASK', ''):
scm_url = self.update_url_auth(scm_url, project.scm_username, if project.scm_type == 'svn':
decrypt_field(project, 'scm_password')) extra_vars['scm_username'] = project.scm_username
extra_vars['scm_password'] = decrypt_field(project, 'scm_password')
else:
scm_url = self.update_url_auth(scm_url, project.scm_username,
decrypt_field(project, 'scm_password'))
elif project.scm_username: elif project.scm_username:
scm_url = self.update_url_auth(scm_url, project.scm_username) if project.scm_type == 'svn':
extra_vars['scm_username'] = project.scm_username
else:
scm_url = self.update_url_auth(scm_url, project.scm_username)
# FIXME: Need to hide password in saved job_args and result_stdout! # FIXME: Need to hide password in saved job_args and result_stdout!
scm_branch = project.scm_branch or {'hg': 'tip'}.get(project.scm_type, 'HEAD') scm_branch = project.scm_branch or {'hg': 'tip'}.get(project.scm_type, 'HEAD')
scm_delete_on_update = project.scm_delete_on_update or project.scm_delete_on_next_update scm_delete_on_update = project.scm_delete_on_update or project.scm_delete_on_next_update
extra_vars = { extra_vars.update({
'project_path': project.get_project_path(check_if_exists=False), 'project_path': project.get_project_path(check_if_exists=False),
'scm_type': project.scm_type, 'scm_type': project.scm_type,
'scm_url': scm_url, 'scm_url': scm_url,
'scm_branch': scm_branch, 'scm_branch': scm_branch,
'scm_clean': project.scm_clean, 'scm_clean': project.scm_clean,
'scm_delete_on_update': scm_delete_on_update, 'scm_delete_on_update': scm_delete_on_update,
} })
args.extend(['-e', json.dumps(extra_vars)]) args.extend(['-e', json.dumps(extra_vars)])
args.append('project_update.yml') args.append('project_update.yml')
@@ -418,9 +436,8 @@ class RunProjectUpdate(BaseTask):
def get_password_prompts(self): def get_password_prompts(self):
d = super(RunProjectUpdate, self).get_password_prompts() d = super(RunProjectUpdate, self).get_password_prompts()
d.update({ d.update({
r'Username for.*:': 'scm_username', # FIXME: Configure whether we should auto accept host keys?
r'Password for.*:': 'scm_password', r'Are you sure you want to continue connecting \(yes/no\)\?': 'yes',
r'Are you sure you want to continue connecting (yes/no)\?': 'yes', # FIXME: Should we really do this?
}) })
return d return d
@@ -441,3 +458,4 @@ class RunProjectUpdate(BaseTask):
Hook for actions after project_update has completed. Hook for actions after project_update has completed.
''' '''
# Start any jobs waiting on this update to finish. # Start any jobs waiting on this update to finish.

View File

@@ -721,6 +721,36 @@ class ProjectUpdatesTest(BaseTransactionTest):
project.save() project.save()
self.check_project_update(project) self.check_project_update(project)
self.assertFalse(os.path.exists(untracked_path)) self.assertFalse(os.path.exists(untracked_path))
# Change username/password for private projects and verify the update
# fails (but doesn't cause the task to hang).
if project.scm_username and project.scm_password not in ('', 'ASK'):
scm_username = project.scm_username
# Clear username only.
project = Project.objects.get(pk=project.pk)
project.scm_username = ''
project.save()
self.check_project_update(project, should_fail=True)
# Try invalid username.
project = Project.objects.get(pk=project.pk)
project.scm_username = 'notavalidusername'
project.save()
self.check_project_update(project, should_fail=True)
# Clear username and password.
project = Project.objects.get(pk=project.pk)
project.scm_username = ''
project.scm_password = ''
project.save()
self.check_project_update(project, should_fail=True)
# Set username, but no password.
project = Project.objects.get(pk=project.pk)
project.scm_username = scm_username
project.save()
self.check_project_update(project, should_fail=True)
# Set username, with invalid password.
project = Project.objects.get(pk=project.pk)
project.scm_password = 'notavalidpassword'
project.save()
self.check_project_update(project, should_fail=True)
def test_public_git_project_over_https(self): def test_public_git_project_over_https(self):
scm_url = getattr(settings, 'TEST_GIT_PUBLIC_HTTPS', scm_url = getattr(settings, 'TEST_GIT_PUBLIC_HTTPS',
@@ -871,3 +901,4 @@ class ProjectUpdatesTest(BaseTransactionTest):
self.assertTrue('scm_key_unlock' in response['passwords_needed_to_update']) self.assertTrue('scm_key_unlock' in response['passwords_needed_to_update'])
with self.current_user(self.super_django_user): with self.current_user(self.super_django_user):
response = self.post(url, {'scm_key_unlock': TEST_SSH_KEY_DATA_UNLOCK}, expect=202) response = self.post(url, {'scm_key_unlock': TEST_SSH_KEY_DATA_UNLOCK}, expect=202)

View File

@@ -7,6 +7,8 @@
# scm_branch: HEAD # scm_branch: HEAD
# scm_clean: true/false # scm_clean: true/false
# scm_delete_on_update: true/false # scm_delete_on_update: true/false
# scm_username: username (only for svn)
# scm_password: password (only for svn)
- hosts: all - hosts: all
connection: local connection: local
@@ -27,4 +29,9 @@
- name: update project using svn - name: update project using svn
subversion: dest={{project_path}} repo={{scm_url}} revision={{scm_branch}} force={{scm_clean}} subversion: dest={{project_path}} repo={{scm_url}} revision={{scm_branch}} force={{scm_clean}}
when: scm_type == 'svn' when: scm_type == 'svn' and not scm_username|default('')
- name: update project using svn with auth
subversion: dest={{project_path}} repo={{scm_url}} revision={{scm_branch}} force={{scm_clean}} username={{scm_username}} password={{scm_password}}
when: scm_type == 'svn' and scm_username|default('')