Start of support for pushing activities from SVN hooks.
This commit is contained in:
@@ -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']}})
|
||||
|
@@ -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'])
|
||||
|
||||
|
@@ -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.
|
||||
|
@@ -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.
|
||||
|
Reference in New Issue
Block a user