Start of support for pushing activities from SVN hooks.

This commit is contained in:
2016-10-25 12:23:52 +02:00
parent 57447cb24c
commit b8d12d1a4d
4 changed files with 111 additions and 10 deletions

View File

@@ -2,7 +2,9 @@
import logging
from pillar.cli import manager
from flask import current_app
from pillar.cli import manager, create_service_account
from pillar.api.utils import authentication
import attract.setup
@@ -22,3 +24,23 @@ def setup_for_attract(project_url, replace=False, svn_url=None):
authentication.force_cli_user()
attract.setup.setup_for_attract(project_url, replace=replace, svn_url=svn_url)
@manager.command
def create_svner_account(email, project_url):
"""Creates an account that can push SVN activity to an Attract project.
:param email: email address associated with the account
:param project_url:
"""
authentication.force_cli_user()
projs_coll = current_app.db()['projects']
proj = projs_coll.find_one({'url': project_url},
projection={'_id': 1})
if not proj:
log.error('Unable to find project url=%s', project_url)
return 1
create_service_account(email, [u'svner'], {'svner': {'project': proj['_id']}})

View File

@@ -1,16 +1,18 @@
import functools
import logging
from flask import Blueprint, render_template, url_for
from flask import Blueprint, render_template, url_for, request, current_app
import flask_login
from pillar.web.utils import attach_project_pictures
from pillar.api.utils import jsonify
from pillar.api.utils import authorization, authentication
import pillar.web.subquery
from pillar.web.system_util import pillar_api
import pillarsdk
import werkzeug.exceptions as wz_exceptions
from attract import current_attract
from attract import current_attract, EXTENSION_NAME
from attract.node_types.task import node_type_task
from attract.node_types.shot import node_type_shot
@@ -38,7 +40,7 @@ def index():
projs_with_summaries = [
(proj, current_attract.shot_manager.shot_status_summary(proj['_id']))
for proj in projects['_items']
]
]
# Fetch all activities for all Attract projects.
id_to_proj = {p['_id']: p for p in projects['_items']}
@@ -168,10 +170,65 @@ def subversion_kick(project, attract_props):
})
@blueprint.route('/api/<project_url>/subversion/log', methods=['POST'])
@authorization.require_login(require_roles={u'service', u'svner'}, require_all=True)
def subversion_log(project_url):
if request.mimetype != 'application/json':
log.debug('Received %s instead of application/json', request.mimetype)
raise wz_exceptions.BadRequest()
# Parse the request
args = request.json
revision = args['revision']
commit_message = args['log']
commit_author = args['author']
current_user_id = authentication.current_user_id()
log.info('Service account %s registers SVN commit %s of user %s',
current_user_id, revision, commit_author)
assert current_user_id
users_coll = current_app.db()['users']
projects_coll = current_app.db()['projects']
project = projects_coll.find_one({'url': project_url},
projection={'_id': 1, 'url': 1,
'extension_props': 1})
if not project:
return 'Project not found', 403
# Check that the service user is allowed to log on this project.
srv_user = users_coll.find_one(current_user_id,
projection={'service.svner': 1})
if srv_user is None:
log.error('subversion_log(%s): current user %s not found -- how did they log in?',
project['url'], current_user_id)
return 'User not found', 403
allowed_project = srv_user.get('service', {}).get('svner', {}).get('project')
if allowed_project != project['_id']:
log.warning('subversion_log(%s): current user %s not authorized to project %s',
project['url'], current_user_id, project['_id'])
return 'Project not allowed', 403
from . import subversion
try:
attract_props = project['extension_props'][EXTENSION_NAME]
except KeyError:
return 'Not set up for Attract', 400
svn_server_url = attract_props['svn_url']
log.info('Re-examining SVN server %s', svn_server_url)
client = subversion.obtain(svn_server_url)
observer = subversion.CommitLogObserver(client)
observer.process_log(revision, commit_author, commit_message)
return 'Registered in Attract'
@blueprint.route('/<project_url>')
@attract_project_view(extension_props=True)
def project_index(project, attract_props):
return render_template('attract/project.html',
project=project,
attract_props=attract_props)
@@ -180,7 +237,6 @@ def project_index(project, attract_props):
@blueprint.route('/<project_url>/help')
@attract_project_view(extension_props=False)
def help(project):
nt_task = project.get_node_type(node_type_task['name'])
nt_shot = project.get_node_type(node_type_shot['name'])

View File

@@ -11,7 +11,13 @@ import svn.common
from pillar import attrs_extra
task_logged = blinker.NamedSignal('task_logged')
task_marker_re = re.compile(r'\[(?P<task_id>T\d+)\]')
shot_logged = blinker.NamedSignal('shot_logged')
marker_re = re.compile(r'\[(?P<type>[TS])(?P<node_id>[0-9a-zA-Z]+)\]')
signals = {
'T': task_logged,
'S': shot_logged,
}
def obtain(server_location):
@@ -47,6 +53,23 @@ class CommitLogObserver(object):
self._log.exception('Error calling self.svn_client.log_default()')
return
def process_log(self, revision, commit_author, commit_message):
"""Obtains task IDs without accessing the SVN server directly."""
self._log.debug('%s: process_log(%s, %s, ...)', self, revision, commit_author)
for node_id, node_type, in self._find_ids(commit_message):
signal = signals[node_type]
signal.send(self, node_id=node_id, log_entry=commit_message, author=commit_author)
def _find_ids(self, message):
# Parse the commit log to see if there are any task/shot markers.
lines = message.split('\n', 100)[:100]
for line in lines:
for match in marker_re.finditer(line):
type = match.group('type')
node_id = match.group('task_id')
yield node_id, type
def _parse_log_entry(self, log_entry):
"""Parses the commit log to see if there are any task markers."""
@@ -57,7 +80,7 @@ class CommitLogObserver(object):
# Parse the commit log to see if there are any task markers.
lines = log_entry.msg.split('\n', 1)
first_line = lines[0]
for match in task_marker_re.finditer(first_line):
for match in marker_re.finditer(first_line):
task_id = match.group('task_id')
# Send a Blinker signal for each observed task identifier.

View File

@@ -14,7 +14,7 @@ from attract.node_types.task import node_type_task
class TaskManager(object):
_log = attrs_extra.log('%s.TaskManager' % __name__)
def task_logged_in_svn(self, sender, task_id, log_entry):
def task_logged_in_svn(self, sender, task_id, log_entry, author):
"""Blinker signal receiver; connects the logged commit with the task.
:param sender: sender of the signal
@@ -23,7 +23,7 @@ class TaskManager(object):
:type task_info: dict
"""
self._log.info("Task '%s' logged in SVN: %s", task_id, log_entry)
self._log.info("Task '%s' logged in SVN by %s: %s", task_id, author, log_entry)
def create_task(self, project, task_type=None, parent=None):
"""Creates a new task, owned by the current user.