You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by he...@apache.org on 2015/05/29 22:40:26 UTC
[04/45] allura git commit: [#7878] Used 2to3 to see what issues would
come up
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/webhooks.py
----------------------------------------------------------------------
diff --git a/webhooks.py b/webhooks.py
new file mode 100644
index 0000000..06971c0
--- /dev/null
+++ b/webhooks.py
@@ -0,0 +1,493 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import logging
+import json
+import hmac
+import hashlib
+import time
+import socket
+import ssl
+
+import requests
+from bson import ObjectId
+from tg import expose, validate, redirect, flash, config
+from tg.decorators import with_trailing_slash, without_trailing_slash
+from pylons import tmpl_context as c
+from pylons import response, request
+from formencode import validators as fev, schema, Invalid
+from ming.odm import session
+from webob import exc
+from pymongo.errors import DuplicateKeyError
+from paste.deploy.converters import asint, aslist
+
+from .allura.controllers import BaseController
+from .allura.lib import helpers as h
+from .allura.lib.decorators import require_post, task
+from .allura.lib.utils import DateJSONEncoder
+from .allura import model as M
+
+
+log = logging.getLogger(__name__)
+
+
+class WebhookValidator(fev.FancyValidator):
+ def __init__(self, sender, app, **kw):
+ self.app = app
+ self.sender = sender
+ super(WebhookValidator, self).__init__(**kw)
+
+ def _to_python(self, value, state):
+ wh = None
+ if isinstance(value, M.Webhook):
+ wh = value
+ elif isinstance(value, ObjectId):
+ wh = M.Webhook.query.get(_id=value)
+ else:
+ try:
+ wh = M.Webhook.query.get(_id=ObjectId(value))
+ except:
+ pass
+ if wh and wh.type == self.sender.type and wh.app_config_id == self.app.config._id:
+ return wh
+ raise Invalid('Invalid webhook', value, state)
+
+
+class WebhookCreateForm(schema.Schema):
+ url = fev.URL(not_empty=True)
+ secret = fev.UnicodeString()
+
+
+class WebhookEditForm(WebhookCreateForm):
+ def __init__(self, sender, app):
+ super(WebhookEditForm, self).__init__()
+ self.add_field('webhook', WebhookValidator(
+ sender=sender, app=app, not_empty=True))
+
+
+class WebhookControllerMeta(type):
+ def __call__(cls, sender, app, *args, **kw):
+ """Decorate post handlers with a validator that references
+ the appropriate webhook sender for this controller.
+ """
+ if hasattr(cls, 'create'):
+ cls.create = validate(
+ cls.create_form(),
+ error_handler=cls.index.__func__,
+ )(cls.create)
+ if hasattr(cls, 'edit'):
+ cls.edit = validate(
+ cls.edit_form(sender, app),
+ error_handler=cls._default.__func__,
+ )(cls.edit)
+ return type.__call__(cls, sender, app, *args, **kw)
+
+
+class WebhookController(BaseController, metaclass=WebhookControllerMeta):
+ create_form = WebhookCreateForm
+ edit_form = WebhookEditForm
+
+ def __init__(self, sender, app):
+ super(WebhookController, self).__init__()
+ self.sender = sender()
+ self.app = app
+
+ def gen_secret(self):
+ return h.cryptographic_nonce(20)
+
+ def update_webhook(self, wh, url, secret=None):
+ if not secret:
+ secret = self.gen_secret()
+ wh.hook_url = url
+ wh.secret = secret
+ try:
+ session(wh).flush(wh)
+ except DuplicateKeyError:
+ session(wh).expunge(wh)
+ msg = '_the_form: "{}" webhook already exists for {} {}'.format(
+ wh.type, self.app.config.options.mount_label, url)
+ raise Invalid(msg, None, None)
+
+ @with_trailing_slash
+ @expose('jinja:allura:templates/webhooks/create_form.html')
+ def index(self, **kw):
+ if not c.form_values and kw:
+ # Executes if update_webhook raises an error
+ c.form_values = {'url': kw.get('url'),
+ 'secret': kw.get('secret')}
+ return {'sender': self.sender,
+ 'action': 'create',
+ 'form': self.create_form()}
+
+ @expose()
+ @require_post()
+ def create(self, url, secret):
+ if self.sender.enforce_limit(self.app):
+ webhook = M.Webhook(
+ type=self.sender.type,
+ app_config_id=self.app.config._id)
+ self.update_webhook(webhook, url, secret)
+ M.AuditLog.log('add webhook %s %s %s',
+ webhook.type, webhook.hook_url,
+ webhook.app_config.url())
+ flash('Created successfully', 'ok')
+ else:
+ flash('You have exceeded the maximum number of webhooks '
+ 'you are allowed to create for this project/app', 'error')
+ redirect(self.app.admin_url + 'webhooks')
+
+ @expose()
+ @require_post()
+ def edit(self, webhook, url, secret):
+ old_url = webhook.hook_url
+ old_secret = webhook.secret
+ self.update_webhook(webhook, url, secret)
+ M.AuditLog.log('edit webhook %s\n%s => %s\n%s',
+ webhook.type, old_url, url,
+ 'secret changed' if old_secret != secret else '')
+ flash('Edited successfully', 'ok')
+ redirect(self.app.admin_url + 'webhooks')
+
+ @expose('json:')
+ @require_post()
+ def delete(self, webhook):
+ form = self.edit_form(self.sender, self.app)
+ try:
+ wh = form.fields['webhook'].to_python(webhook)
+ except Invalid:
+ raise exc.HTTPNotFound()
+ wh.delete()
+ M.AuditLog.log('delete webhook %s %s %s',
+ wh.type, wh.hook_url, wh.app_config.url())
+ return {'status': 'ok'}
+
+ @without_trailing_slash
+ @expose('jinja:allura:templates/webhooks/create_form.html')
+ def _default(self, webhook, **kw):
+ form = self.edit_form(self.sender, self.app)
+ try:
+ wh = form.fields['webhook'].to_python(webhook)
+ except Invalid:
+ raise exc.HTTPNotFound()
+ c.form_values = {'url': kw.get('url') or wh.hook_url,
+ 'secret': kw.get('secret') or wh.secret,
+ 'webhook': str(wh._id)}
+ return {'sender': self.sender,
+ 'action': 'edit',
+ 'form': form}
+
+
+class WebhookRestController(BaseController):
+ def __init__(self, sender, app):
+ super(WebhookRestController, self).__init__()
+ self.sender = sender()
+ self.app = app
+ self.create_form = WebhookController.create_form
+ self.edit_form = WebhookController.edit_form
+
+ def _error(self, e):
+ error = getattr(e, 'error_dict', None)
+ if error:
+ _error = {}
+ for k, v in list(error.items()):
+ _error[k] = str(v)
+ return _error
+ error = getattr(e, 'msg', None)
+ if not error:
+ error = getattr(e, 'message', '')
+ return error
+
+ def update_webhook(self, wh, url, secret=None):
+ controller = WebhookController(self.sender.__class__, self.app)
+ controller.update_webhook(wh, url, secret)
+
+ @expose('json:')
+ @require_post()
+ def index(self, **kw):
+ response.content_type = 'application/json'
+ try:
+ params = {'secret': kw.pop('secret', ''),
+ 'url': kw.pop('url', None)}
+ valid = self.create_form().to_python(params)
+ except Exception as e:
+ response.status_int = 400
+ return {'result': 'error', 'error': self._error(e)}
+ if self.sender.enforce_limit(self.app):
+ webhook = M.Webhook(
+ type=self.sender.type,
+ app_config_id=self.app.config._id)
+ try:
+ self.update_webhook(webhook, valid['url'], valid['secret'])
+ except Invalid as e:
+ response.status_int = 400
+ return {'result': 'error', 'error': self._error(e)}
+ M.AuditLog.log('add webhook %s %s %s',
+ webhook.type, webhook.hook_url,
+ webhook.app_config.url())
+ response.status_int = 201
+ # refetch updated values (e.g. mod_date)
+ session(webhook).expunge(webhook)
+ webhook = M.Webhook.query.get(_id=webhook._id)
+ return webhook.__json__()
+ else:
+ limits = {
+ 'max': M.Webhook.max_hooks(
+ self.sender.type,
+ self.app.config.tool_name),
+ 'used': M.Webhook.query.find({
+ 'type': self.sender.type,
+ 'app_config_id': self.app.config._id,
+ }).count(),
+ }
+ resp = {
+ 'result': 'error',
+ 'error': 'You have exceeded the maximum number of webhooks '
+ 'you are allowed to create for this project/app',
+ 'limits': limits,
+ }
+ response.status_int = 400
+ return resp
+
+ @expose('json:')
+ def _default(self, webhook, **kw):
+ form = self.edit_form(self.sender, self.app)
+ try:
+ wh = form.fields['webhook'].to_python(webhook)
+ except Invalid:
+ raise exc.HTTPNotFound()
+ if request.method == 'POST':
+ return self._edit(wh, form, **kw)
+ elif request.method == 'DELETE':
+ return self._delete(wh)
+ else:
+ return wh.__json__()
+
+ def _edit(self, webhook, form, **kw):
+ old_secret = webhook.secret
+ old_url = webhook.hook_url
+ try:
+ params = {'secret': kw.pop('secret', old_secret),
+ 'url': kw.pop('url', old_url),
+ 'webhook': str(webhook._id)}
+ valid = form.to_python(params)
+ except Exception as e:
+ response.status_int = 400
+ return {'result': 'error', 'error': self._error(e)}
+ try:
+ self.update_webhook(webhook, valid['url'], valid['secret'])
+ except Invalid as e:
+ response.status_int = 400
+ return {'result': 'error', 'error': self._error(e)}
+ M.AuditLog.log(
+ 'edit webhook %s\n%s => %s\n%s',
+ webhook.type, old_url, valid['url'],
+ 'secret changed' if old_secret != valid['secret'] else '')
+ # refetch updated values (e.g. mod_date)
+ session(webhook).expunge(webhook)
+ webhook = M.Webhook.query.get(_id=webhook._id)
+ return webhook.__json__()
+
+ def _delete(self, webhook):
+ webhook.delete()
+ M.AuditLog.log(
+ 'delete webhook %s %s %s',
+ webhook.type,
+ webhook.hook_url,
+ webhook.app_config.url())
+ return {'result': 'ok'}
+
+
+class SendWebhookHelper(object):
+ def __init__(self, webhook, payload):
+ self.webhook = webhook
+ self.payload = payload
+
+ @property
+ def timeout(self):
+ return asint(config.get('webhook.timeout', 30))
+
+ @property
+ def retries(self):
+ t = aslist(config.get('webhook.retry', [60, 120, 240]))
+ return list(map(int, t))
+
+ def sign(self, json_payload):
+ signature = hmac.new(
+ self.webhook.secret.encode('utf-8'),
+ json_payload.encode('utf-8'),
+ hashlib.sha1)
+ return 'sha1=' + signature.hexdigest()
+
+ def log_msg(self, msg, response=None):
+ message = '{}: {} {} {}'.format(
+ msg,
+ self.webhook.type,
+ self.webhook.hook_url,
+ self.webhook.app_config.url())
+ if response is not None:
+ message = '{} {} {} {}'.format(
+ message,
+ response.status_code,
+ response.text,
+ response.headers)
+ return message
+
+ def send(self):
+ json_payload = json.dumps(self.payload, cls=DateJSONEncoder)
+ signature = self.sign(json_payload)
+ headers = {'content-type': 'application/json',
+ 'User-Agent': 'Allura Webhook (https://allura.apache.org/)',
+ 'X-Allura-Signature': signature}
+ ok = self._send(self.webhook.hook_url, json_payload, headers)
+ if not ok:
+ log.info('Retrying webhook in: %s', self.retries)
+ for t in self.retries:
+ log.info('Retrying webhook in %s seconds', t)
+ time.sleep(t)
+ ok = self._send(self.webhook.hook_url, json_payload, headers)
+ if ok:
+ return
+
+ def _send(self, url, data, headers):
+ try:
+ r = requests.post(
+ url,
+ data=data,
+ headers=headers,
+ timeout=self.timeout)
+ except (requests.exceptions.RequestException,
+ socket.timeout,
+ ssl.SSLError):
+ log.exception(self.log_msg('Webhook send error'))
+ return False
+ if r.status_code >= 200 and r.status_code < 300:
+ log.info(self.log_msg('Webhook successfully sent'))
+ return True
+ else:
+ log.error(self.log_msg('Webhook send error', response=r))
+ return False
+
+
+@task()
+def send_webhook(webhook_id, payload):
+ webhook = M.Webhook.query.get(_id=webhook_id)
+ SendWebhookHelper(webhook, payload).send()
+
+
+class WebhookSender(object):
+ """Base class for webhook senders.
+
+ Subclasses are required to implement :meth:`get_payload()` and set
+ :attr:`type` and :attr:`triggered_by`.
+ """
+
+ type = None
+ triggered_by = []
+ controller = WebhookController
+ api_controller = WebhookRestController
+
+ def get_payload(self, **kw):
+ """Return a dict with webhook payload"""
+ raise NotImplementedError('get_payload')
+
+ def send(self, params_or_list):
+ """Post a task that will send webhook payload
+
+ :param params_or_list: dict with keyword parameters to be passed to
+ :meth:`get_payload` or a list of such dicts. If it's a list for each
+ element appropriate payload will be submitted, but limit will be
+ enforced only once for each webhook.
+ """
+ if not isinstance(params_or_list, list):
+ params_or_list = [params_or_list]
+ webhooks = M.Webhook.query.find(dict(
+ app_config_id=c.app.config._id,
+ type=self.type,
+ )).all()
+ if webhooks:
+ payloads = [self.get_payload(**params)
+ for params in params_or_list]
+ for webhook in webhooks:
+ if webhook.enforce_limit():
+ webhook.update_limit()
+ for payload in payloads:
+ send_webhook.post(webhook._id, payload)
+ else:
+ log.warn('Webhook fires too often: %s. Skipping', webhook)
+
+ def enforce_limit(self, app):
+ '''
+ Checks if limit of webhooks created for given project/app is reached.
+ Returns False if limit is reached, True otherwise.
+ '''
+ count = M.Webhook.query.find(dict(
+ app_config_id=app.config._id,
+ type=self.type,
+ )).count()
+ limit = M.Webhook.max_hooks(self.type, app.config.tool_name)
+ return count < limit
+
+
+class RepoPushWebhookSender(WebhookSender):
+ type = 'repo-push'
+ triggered_by = ['git', 'hg', 'svn']
+
+ def _before(self, repo, commit_ids):
+ if len(commit_ids) > 0:
+ ci = commit_ids[-1]
+ parents = repo.commit(ci).parent_ids
+ if len(parents) > 0:
+ # Merge commit will have multiple parents. As far as I can tell
+ # the last one will be the branch head before merge
+ return self._convert_id(parents[-1])
+ return ''
+
+ def _after(self, commit_ids):
+ if len(commit_ids) > 0:
+ return self._convert_id(commit_ids[0])
+ return ''
+
+ def _convert_id(self, _id):
+ if ':' in _id:
+ _id = 'r' + _id.rsplit(':', 1)[1]
+ return _id
+
+ def get_payload(self, commit_ids, **kw):
+ app = kw.get('app') or c.app
+ commits = [app.repo.commit(ci).webhook_info for ci in commit_ids]
+ for ci in commits:
+ ci['id'] = self._convert_id(ci['id'])
+ before = self._before(app.repo, commit_ids)
+ after = self._after(commit_ids)
+ payload = {
+ 'size': len(commits),
+ 'commits': commits,
+ 'before': before,
+ 'after': after,
+ 'repository': {
+ 'name': app.config.options.mount_label,
+ 'full_name': app.url,
+ 'url': h.absurl(app.url),
+ },
+ }
+ if kw.get('ref'):
+ payload['ref'] = kw['ref']
+ return payload
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/websetup/__init__.py
----------------------------------------------------------------------
diff --git a/websetup/__init__.py b/websetup/__init__.py
new file mode 100644
index 0000000..cc62fba
--- /dev/null
+++ b/websetup/__init__.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""Setup the allura application"""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import logging
+
+from allura.config.environment import load_environment
+
+__all__ = ['setup_app']
+
+log = logging.getLogger(__name__)
+
+from .schema import setup_schema
+from . import bootstrap
+
+
+def setup_app(command, conf, vars):
+ """Place any commands to setup allura here"""
+ load_environment(conf.global_conf, conf.local_conf)
+ setup_schema(command, conf, vars)
+ bootstrap.bootstrap(command, conf, vars)
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/websetup/bootstrap.py
----------------------------------------------------------------------
diff --git a/websetup/bootstrap.py b/websetup/bootstrap.py
new file mode 100644
index 0000000..4a209ef
--- /dev/null
+++ b/websetup/bootstrap.py
@@ -0,0 +1,357 @@
+# -*- coding: utf-8 -*-
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""Setup the allura application"""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+import os
+import sys
+import logging
+import shutil
+from collections import defaultdict
+from textwrap import dedent
+
+import tg
+from pylons import tmpl_context as c, app_globals as g
+from paste.deploy.converters import asbool
+import ew
+
+from ming import Session, mim
+from ming.orm import state, session
+from ming.orm.ormsession import ThreadLocalORMSession
+
+import allura
+from allura.lib import plugin
+from allura.lib import helpers as h
+from allura import model as M
+from allura.command import EnsureIndexCommand
+from allura.command import CreateTroveCategoriesCommand
+from allura.websetup.schema import REGISTRY
+
+from forgewiki import model as WM
+
+log = logging.getLogger(__name__)
+
+
+def cache_test_data():
+ log.info('Saving data to cache in .test-data')
+ if os.path.exists('.test-data'):
+ shutil.rmtree('.test-data')
+ os.system(
+ 'mongodump -h 127.0.0.1:27018 -o .test-data > mongodump.log 2>&1')
+
+
+def restore_test_data():
+ if os.path.exists('.test-data'):
+ log.info('Restoring data from cache in .test-data')
+ rc = os.system(
+ 'mongorestore -h 127.0.0.1:27018 --dir .test-data > mongorestore.log 2>&1')
+ return rc == 0
+ else:
+ return False
+
+
+def bootstrap(command, conf, vars):
+ """Place any commands to setup allura here"""
+ # are we being called by the test suite?
+ test_run = conf.get('__file__', '').endswith('test.ini')
+
+ if not test_run:
+ # when this is run via the `setup-app` cmd, some macro rendering needs this set up
+ REGISTRY.register(ew.widget_context,
+ ew.core.WidgetContext('http', ew.ResourceManager()))
+
+ # if this is a test_run, skip user project creation to save time
+ make_user_projects = not test_run
+
+ def make_user(*args, **kw):
+ kw.update(make_project=make_user_projects)
+ return create_user(*args, **kw)
+
+ # Temporarily disable auth extensions to prevent unintended side-effects
+ tg.config['auth.method'] = tg.config['registration.method'] = 'local'
+ assert tg.config['auth.method'] == 'local'
+ conf['auth.method'] = conf['registration.method'] = 'local'
+
+ # Clean up all old stuff
+ ThreadLocalORMSession.close_all()
+ c.user = c.project = c.app = None
+ wipe_database()
+ try:
+ g.solr.delete(q='*:*')
+ except: # pragma no cover
+ log.error('SOLR server is %s', g.solr_server)
+ log.error('Error clearing solr index')
+
+ # set up mongo indexes
+ index = EnsureIndexCommand('ensure_index')
+ index.run([''])
+
+ if asbool(conf.get('cache_test_data')):
+ if restore_test_data():
+ h.set_context('test', neighborhood='Projects')
+ return
+ log.info('Initializing search')
+
+ log.info('Registering root user & default neighborhoods')
+ anonymous = M.User(
+ _id=None,
+ username='*anonymous',
+ display_name='Anonymous')
+
+ # never make a user project for the root user
+ root = create_user('Root', make_project=False)
+
+ n_projects = M.Neighborhood(name='Projects', url_prefix='/p/',
+ features=dict(private_projects=True,
+ max_projects=None,
+ css='none',
+ google_analytics=False))
+ n_users = M.Neighborhood(name='Users', url_prefix='/u/',
+ shortname_prefix='u/',
+ anchored_tools='profile:Profile,userstats:Statistics',
+ features=dict(private_projects=True,
+ max_projects=None,
+ css='none',
+ google_analytics=False))
+ n_adobe = M.Neighborhood(
+ name='Adobe', url_prefix='/adobe/', project_list_url='/adobe/',
+ features=dict(private_projects=True,
+ max_projects=None,
+ css='custom',
+ google_analytics=True))
+ assert tg.config['auth.method'] == 'local'
+ project_reg = plugin.ProjectRegistrationProvider.get()
+ p_projects = project_reg.register_neighborhood_project(
+ n_projects, [root], allow_register=True)
+ p_users = project_reg.register_neighborhood_project(n_users, [root])
+ p_adobe = project_reg.register_neighborhood_project(n_adobe, [root])
+
+ def set_nbhd_wiki_content(nbhd_proj, content):
+ wiki = nbhd_proj.app_instance('wiki')
+ page = WM.Page.query.get(
+ app_config_id=wiki.config._id, title=wiki.root_page_name)
+ page.text = content
+
+ set_nbhd_wiki_content(p_projects, dedent('''
+ Welcome to the "Projects" neighborhood. It is the default neighborhood in Allura.
+ You can edit this wiki page as you see fit. Here's a few ways to get started:
+
+ [Register a new project](/p/add_project)
+
+ [Neighborhood administration](/p/admin)
+
+ [[projects show_total=yes]]
+ '''))
+ set_nbhd_wiki_content(p_users, dedent('''
+ This is the "Users" neighborhood. All users automatically get a user-project created for them, using their username.
+
+ [Neighborhood administration](/u/admin)
+
+ [[projects show_total=yes]]
+ '''))
+ set_nbhd_wiki_content(p_adobe, dedent('''
+ This is the "Adobe" neighborhood. It is just an example of having projects in a different neighborhood.
+
+ [Neighborhood administration](/adobe/admin)
+
+ [[projects show_total=yes]]
+ '''))
+
+ ThreadLocalORMSession.flush_all()
+ ThreadLocalORMSession.close_all()
+
+ # add the adobe icon
+ file_name = 'adobe_icon.png'
+ file_path = os.path.join(
+ allura.__path__[0], 'public', 'nf', 'images', file_name)
+ M.NeighborhoodFile.from_path(file_path, neighborhood_id=n_adobe._id)
+
+ # Add some test users
+ for unum in range(10):
+ make_user('Test User %d' % unum)
+
+ log.info('Creating basic project categories')
+ cat1 = M.ProjectCategory(name='clustering', label='Clustering')
+
+ cat2 = M.ProjectCategory(name='communications', label='Communications')
+ cat2_1 = M.ProjectCategory(
+ name='synchronization', label='Synchronization', parent_id=cat2._id)
+ cat2_2 = M.ProjectCategory(
+ name='streaming', label='Streaming', parent_id=cat2._id)
+ cat2_3 = M.ProjectCategory(name='fax', label='Fax', parent_id=cat2._id)
+ cat2_4 = M.ProjectCategory(name='bbs', label='BBS', parent_id=cat2._id)
+
+ cat3 = M.ProjectCategory(name='database', label='Database')
+ cat3_1 = M.ProjectCategory(
+ name='front_ends', label='Front-Ends', parent_id=cat3._id)
+ cat3_2 = M.ProjectCategory(
+ name='engines_servers', label='Engines/Servers', parent_id=cat3._id)
+
+ log.info('Registering "regular users" (non-root) and default projects')
+ # since this runs a lot for tests, separate test and default users and
+ # do the minimal needed
+ if asbool(conf.get('load_test_data')):
+ u_admin = make_user('Test Admin')
+ u_admin.preferences = dict(email_address='test-admin@users.localhost')
+ u_admin.email_addresses = ['test-admin@users.localhost']
+ u_admin.set_password('foo')
+ u_admin.claim_address('test-admin@users.localhost')
+ ThreadLocalORMSession.flush_all()
+
+ admin_email = M.EmailAddress.get(email='test-admin@users.localhost')
+ admin_email.confirmed = True
+ else:
+ u_admin = make_user('Admin 1', username='admin1')
+ # Admin1 is almost root, with admin access for Users and Projects
+ # neighborhoods
+ p_projects.add_user(u_admin, ['Admin'])
+ p_users.add_user(u_admin, ['Admin'])
+
+ p_allura = n_projects.register_project('allura', u_admin, 'Allura')
+ u1 = make_user('Test User')
+ p_adobe1 = n_adobe.register_project('adobe-1', u_admin, 'Adobe project 1')
+ p_adobe.add_user(u_admin, ['Admin'])
+ p0 = n_projects.register_project('test', u_admin, 'Test Project')
+ p1 = n_projects.register_project('test2', u_admin, 'Test 2')
+ p0._extra_tool_status = ['alpha', 'beta']
+
+ sess = session(M.Neighborhood) # all the sessions are the same
+ for x in (n_adobe, n_projects, n_users, p_projects, p_users, p_adobe):
+ # Ming doesn't detect substructural changes in newly created objects
+ # (vs loaded from DB)
+ state(x).status = 'dirty'
+ # TODO: Hope that Ming can be improved to at least avoid stuff below
+ sess.flush(x)
+
+ ThreadLocalORMSession.flush_all()
+
+ if asbool(conf.get('load_test_data')):
+ if asbool(conf.get('cache_test_data')):
+ cache_test_data()
+ else: # pragma no cover
+ # regular first-time setup
+
+ create_trove_categories = CreateTroveCategoriesCommand('create_trove_categories')
+ create_trove_categories.run([''])
+
+ p0.add_user(u_admin, ['Admin'])
+ log.info('Registering initial apps')
+ with h.push_config(c, user=u_admin):
+ p0.install_apps([{'ep_name': ep_name}
+ for ep_name, app in g.entry_points['tool'].items()
+ if app._installable(tool_name=ep_name,
+ nbhd=n_projects,
+ project_tools=[])
+ ])
+
+ # reload our p0 project so that p0.app_configs is accurate with all the
+ # newly installed apps
+ ThreadLocalORMSession.flush_all()
+ ThreadLocalORMSession.close_all()
+ p0 = M.Project.query.get(_id=p0._id)
+ sub = p0.new_subproject('sub1', project_name='A Subproject')
+ with h.push_config(c, user=u_admin):
+ sub.install_app('wiki')
+
+ ThreadLocalORMSession.flush_all()
+ ThreadLocalORMSession.close_all()
+
+
+def wipe_database():
+ conn = M.main_doc_session.bind.conn
+ if isinstance(conn, mim.Connection):
+ clear_all_database_tables()
+ for db in conn.database_names():
+ db = conn[db]
+ else:
+ for database in conn.database_names():
+ if database not in ('allura', 'pyforge', 'project-data'):
+ continue
+ log.info('Wiping database %s', database)
+ db = conn[database]
+ for coll in db.collection_names():
+ if coll.startswith('system.'):
+ continue
+ log.info('Dropping collection %s:%s', database, coll)
+ try:
+ db.drop_collection(coll)
+ except:
+ pass
+
+
+def clear_all_database_tables():
+ conn = M.main_doc_session.bind.conn
+ for db in conn.database_names():
+ db = conn[db]
+ for coll in db.collection_names():
+ if coll == 'system.indexes':
+ continue
+ db.drop_collection(coll)
+
+
+def create_user(display_name, username=None, password='foo', make_project=False):
+ if not username:
+ username = display_name.lower().replace(' ', '-')
+ user = M.User.register(dict(username=username,
+ display_name=display_name),
+ make_project=make_project)
+ user.set_password(password)
+ return user
+
+
+class DBSession(Session):
+
+ '''Simple session that takes a pymongo connection and a database name'''
+
+ def __init__(self, db):
+ self._db = db
+
+ @property
+ def db(self):
+ return self._db
+
+ def _impl(self, cls):
+ return self.db[cls.__mongometa__.name]
+
+
+def pm(etype, value, tb): # pragma no cover
+ import pdb
+ import traceback
+ try:
+ from IPython.ipapi import make_session
+ make_session()
+ from IPython.Debugger import Pdb
+ sys.stderr.write('Entering post-mortem IPDB shell\n')
+ p = Pdb(color_scheme='Linux')
+ p.reset()
+ p.setup(None, tb)
+ p.print_stack_trace()
+ sys.stderr.write('%s: %s\n' % (etype, value))
+ p.cmdloop()
+ p.forget()
+ # p.interaction(None, tb)
+ except ImportError:
+ sys.stderr.write('Entering post-mortem PDB shell\n')
+ traceback.print_exception(etype, value, tb)
+ pdb.post_mortem(tb)
+
+sys.excepthook = pm
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/websetup/schema.py
----------------------------------------------------------------------
diff --git a/websetup/schema.py b/websetup/schema.py
new file mode 100644
index 0000000..acf65d0
--- /dev/null
+++ b/websetup/schema.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""Setup the allura application"""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import logging
+
+import activitystream
+from tg import config
+import pylons
+from paste.deploy.converters import asbool
+from paste.registry import Registry
+
+log = logging.getLogger(__name__)
+REGISTRY = Registry()
+
+
+def setup_schema(command, conf, vars):
+ """Place any commands to setup allura here"""
+ import ming
+ import allura
+
+ REGISTRY.prepare()
+ REGISTRY.register(pylons.tmpl_context, EmptyClass())
+ REGISTRY.register(pylons.app_globals, config['pylons.app_globals'])
+ REGISTRY.register(allura.credentials, allura.lib.security.Credentials())
+ ming.configure(**conf)
+ if asbool(conf.get('activitystream.recording.enabled', False)):
+ activitystream.configure(**conf)
+ # Nothing to do
+ log.info('setup_schema called')
+
+
+class EmptyClass(object):
+ pass