##// END OF EJS Templates
Made config file free configuration based on database and capable of beeing manage via application settings + some code cleanups
marcink -
r341:1ef52a70 default
parent child Browse files
Show More
@@ -1,70 +1,73 b''
1 1 """Pylons environment configuration"""
2 2 from mako.lookup import TemplateLookup
3 3 from pylons.configuration import PylonsConfig
4 4 from pylons.error import handle_mako_error
5 5 from pylons_app.config.routing import make_map
6 6 from pylons_app.lib.auth import set_available_permissions, set_base_path
7 from pylons_app.lib.utils import repo2db_mapper
7 from pylons_app.lib.utils import repo2db_mapper, make_ui, set_hg_app_config
8 8 from pylons_app.model import init_model
9 9 from pylons_app.model.hg_model import _get_repos_cached_initial
10 10 from sqlalchemy import engine_from_config
11 11 import logging
12 12 import os
13 13 import pylons_app.lib.app_globals as app_globals
14 14 import pylons_app.lib.helpers
15 15
16 16 log = logging.getLogger(__name__)
17 17
18 18 def load_environment(global_conf, app_conf):
19 19 """Configure the Pylons environment via the ``pylons.config``
20 20 object
21 21 """
22 22 config = PylonsConfig()
23 23
24 24 # Pylons paths
25 25 root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
26 26 paths = dict(root=root,
27 27 controllers=os.path.join(root, 'controllers'),
28 28 static_files=os.path.join(root, 'public'),
29 29 templates=[os.path.join(root, 'templates')])
30 30
31 31 # Initialize config with the basic options
32 32 config.init_app(global_conf, app_conf, package='pylons_app', paths=paths)
33 33
34 34 config['routes.map'] = make_map(config)
35 35 config['pylons.app_globals'] = app_globals.Globals(config)
36 36 config['pylons.h'] = pylons_app.lib.helpers
37 37
38 38 # Setup cache object as early as possible
39 39 import pylons
40 40 pylons.cache._push_object(config['pylons.app_globals'].cache)
41 41
42 42 # Create the Mako TemplateLookup, with the default auto-escaping
43 43 config['pylons.app_globals'].mako_lookup = TemplateLookup(
44 44 directories=paths['templates'],
45 45 error_handler=handle_mako_error,
46 46 module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
47 47 input_encoding='utf-8', default_filters=['escape'],
48 48 imports=['from webhelpers.html import escape'])
49 49
50 50 #sets the c attribute access when don't existing attribute are accessed
51 51 config['pylons.strict_tmpl_context'] = True
52 52
53 53 #MULTIPLE DB configs
54 54 # Setup the SQLAlchemy database engine
55 55 if config['debug']:
56 56 #use query time debugging.
57 57 from pylons_app.lib.timerproxy import TimerProxy
58 58 sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.',
59 59 proxy=TimerProxy())
60 60 else:
61 61 sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.')
62 62
63 63 init_model(sa_engine_db1)
64 config['pylons.app_globals'].baseui = make_ui('db')
65
64 66 repo2db_mapper(_get_repos_cached_initial(config['pylons.app_globals']))
65 67 set_available_permissions(config)
66 68 set_base_path(config)
69 set_hg_app_config(config)
67 70 # CONFIGURATION OPTIONS HERE (note: all config options will override
68 71 # any Pylons config options)
69 72
70 73 return config
@@ -1,73 +1,72 b''
1 1 """Pylons middleware initialization"""
2 2 from beaker.middleware import SessionMiddleware
3 3 from paste.cascade import Cascade
4 4 from paste.registry import RegistryManager
5 5 from paste.urlparser import StaticURLParser
6 6 from paste.deploy.converters import asbool
7 7 from pylons.middleware import ErrorHandler, StatusCodeRedirect
8 8 from pylons.wsgiapp import PylonsApp
9 9 from routes.middleware import RoutesMiddleware
10 10 from pylons_app.lib.middleware.simplehg import SimpleHg
11 11 from pylons_app.lib.middleware.https_fixup import HttpsFixup
12 12 from pylons_app.config.environment import load_environment
13 13
14 14 def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
15 15 """Create a Pylons WSGI application and return it
16 16
17 17 ``global_conf``
18 18 The inherited configuration for this application. Normally from
19 19 the [DEFAULT] section of the Paste ini file.
20 20
21 21 ``full_stack``
22 22 Whether or not this application provides a full WSGI stack (by
23 23 default, meaning it handles its own exceptions and errors).
24 24 Disable full_stack when this application is "managed" by
25 25 another WSGI middleware.
26 26
27 27 ``app_conf``
28 28 The application's local configuration. Normally specified in
29 29 the [app:<name>] section of the Paste ini file (where <name>
30 30 defaults to main).
31 31
32 32 """
33 33 # Configure the Pylons environment
34 34 config = load_environment(global_conf, app_conf)
35 35
36 36 # The Pylons WSGI app
37 37 app = PylonsApp(config=config)
38 38
39 39 # Routing/Session/Cache Middleware
40 40 app = RoutesMiddleware(app, config['routes.map'])
41 41 app = SessionMiddleware(app, config)
42 42
43 43 # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
44 #set the https based on HTTP_X_URL_SCHEME
45 44
46 45 app = SimpleHg(app, config)
47 46
48 47 if asbool(full_stack):
49 48 # Handle Python exceptions
50 49 app = ErrorHandler(app, global_conf, **config['pylons.errorware'])
51 50
52 51 # Display error documents for 401, 403, 404 status codes (and
53 52 # 500 when debug is disabled)
54 53 if asbool(config['debug']):
55 54 app = StatusCodeRedirect(app)
56 55 else:
57 56 app = StatusCodeRedirect(app, [400, 401, 403, 404, 500])
58 57
59 58 #enable https redirets based on HTTP_X_URL_SCHEME set by proxy
60 59 app = HttpsFixup(app)
61 60
62 61 # Establish the Registry for this application
63 62 app = RegistryManager(app)
64 63
65 64 if asbool(static_files):
66 65 # Serve static files
67 66 static_app = StaticURLParser(config['pylons.paths']['static_files'])
68 67 app = Cascade([static_app, app])
69 68
70 69 app.config = config
71 70
72 71 return app
73 72
@@ -1,97 +1,95 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # settings controller for pylons
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on June 30, 2010
22 22 settings controller for pylons
23 23 @author: marcink
24 24 """
25 25 from formencode import htmlfill
26 26 from pylons import tmpl_context as c, request, url
27 27 from pylons.controllers.util import redirect
28 28 from pylons.i18n.translation import _
29 29 from pylons_app.lib.auth import LoginRequired, HasRepoPermissionAllDecorator
30 30 from pylons_app.lib.base import BaseController, render
31 31 from pylons_app.lib.utils import invalidate_cache
32 32 from pylons_app.model.forms import RepoSettingsForm
33 33 from pylons_app.model.repo_model import RepoModel
34 34 import formencode
35 35 import logging
36 36 import pylons_app.lib.helpers as h
37 37 log = logging.getLogger(__name__)
38 38
39 39 class SettingsController(BaseController):
40 40
41 41 @LoginRequired()
42 42 @HasRepoPermissionAllDecorator('repository.admin')
43 43 def __before__(self):
44 44 super(SettingsController, self).__before__()
45 45
46 46 def index(self, repo_name):
47 47 repo_model = RepoModel()
48 48 c.repo_info = repo = repo_model.get(repo_name)
49 49 if not repo:
50 50 h.flash(_('%s repository is not mapped to db perhaps'
51 51 ' it was created or renamed from the filesystem'
52 52 ' please run the application again'
53 53 ' in order to rescan repositories') % repo_name,
54 54 category='error')
55 55
56 56 return redirect(url('repos'))
57 57 defaults = c.repo_info.__dict__
58 58 defaults.update({'user':c.repo_info.user.username})
59 59 c.users_array = repo_model.get_users_js()
60 60
61 61 for p in c.repo_info.repo2perm:
62 62 defaults.update({'perm_%s' % p.user.username:
63 63 p.permission.permission_name})
64 64
65 65 return htmlfill.render(
66 66 render('settings/repo_settings.html'),
67 67 defaults=defaults,
68 68 encoding="UTF-8",
69 69 force_defaults=False
70 70 )
71 71
72 72 def update(self, repo_name):
73 print request.POST
74 print 'x' * 110
75 73 repo_model = RepoModel()
76 74 _form = RepoSettingsForm(edit=True)()
77 75 try:
78 76 form_result = _form.to_python(dict(request.POST))
79 77 repo_model.update(repo_name, form_result)
80 78 invalidate_cache('cached_repo_list')
81 79 h.flash(_('Repository %s updated succesfully' % repo_name),
82 80 category='success')
83 81
84 82 except formencode.Invalid as errors:
85 83 c.repo_info = repo_model.get(repo_name)
86 84 c.users_array = repo_model.get_users_js()
87 85 errors.value.update({'user':c.repo_info.user.username})
88 86 c.form_errors = errors.error_dict
89 87 return htmlfill.render(
90 88 render('admin/repos/repo_edit.html'),
91 89 defaults=errors.value,
92 90 encoding="UTF-8")
93 91 except Exception:
94 92 h.flash(_('error occured during update of repository %s') \
95 93 % form_result['repo_name'], category='error')
96 94
97 95 return redirect(url('repo_settings_home', repo_name=repo_name))
@@ -1,25 +1,33 b''
1 1 """The application's Globals object"""
2 2
3 3 from beaker.cache import CacheManager
4 4 from beaker.util import parse_cache_config_options
5 from pylons_app.lib.utils import make_ui
5 from vcs.utils.lazy import LazyProperty
6 6
7 7 class Globals(object):
8
9 8 """Globals acts as a container for objects available throughout the
10 9 life of the application
11 10
12 11 """
13 12
14 13 def __init__(self, config):
15 14 """One instance of Globals is created during application
16 15 initialization and is available during requests via the
17 16 'app_globals' variable
18 17
19 18 """
20 19 self.cache = CacheManager(**parse_cache_config_options(config))
21 self.baseui = make_ui(config['hg_app_repo_conf'])
22 self.paths = self.baseui.configitems('paths')
23 self.base_path = self.paths[0][1].replace('*', '')
24 20 self.changeset_annotation_colors = {}
25 self.available_permissions = None # propagated after init_model
21 self.available_permissions = None # propagated after init_model
22 self.app_title = None # propagated after init_model
23 self.baseui = None # propagated after init_model
24
25 @LazyProperty
26 def paths(self):
27 if self.baseui:
28 return self.baseui.configitems('paths')
29
30 @LazyProperty
31 def base_path(self):
32 if self.baseui:
33 return self.paths[0][1].replace('*', '')
@@ -1,134 +1,188 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # database managment for hg app
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20
21 21 """
22 22 Created on April 10, 2010
23 23 database managment and creation for hg app
24 24 @author: marcink
25 25 """
26 26
27 27 from os.path import dirname as dn, join as jn
28 28 import os
29 29 import sys
30 30 import uuid
31 31 ROOT = dn(dn(dn(os.path.realpath(__file__))))
32 32 sys.path.append(ROOT)
33 33
34 34 from pylons_app.lib.auth import get_crypt_password
35 35 from pylons_app.model import init_model
36 from pylons_app.model.db import User, Permission
36 from pylons_app.model.db import User, Permission, HgAppUi
37 37 from pylons_app.model.meta import Session, Base
38 38 from sqlalchemy.engine import create_engine
39 39 import logging
40 40
41 41 log = logging.getLogger('db manage')
42 42 log.setLevel(logging.DEBUG)
43 43 console_handler = logging.StreamHandler()
44 44 console_handler.setFormatter(logging.Formatter("%(asctime)s.%(msecs)03d"
45 45 " %(levelname)-5.5s [%(name)s] %(message)s"))
46 46 log.addHandler(console_handler)
47 47
48 48 class DbManage(object):
49 49 def __init__(self, log_sql):
50 50 self.dbname = 'hg_app.db'
51 51 dburi = 'sqlite:////%s' % jn(ROOT, self.dbname)
52 52 engine = create_engine(dburi, echo=log_sql)
53 53 init_model(engine)
54 54 self.sa = Session()
55 55 self.db_exists = False
56 56
57 57 def check_for_db(self, override):
58 58 log.info('checking for exisiting db')
59 59 if os.path.isfile(jn(ROOT, self.dbname)):
60 60 self.db_exists = True
61 61 log.info('database exisist')
62 62 if not override:
63 63 raise Exception('database already exists')
64 64
65 65 def create_tables(self, override=False):
66 66 """
67 67 Create a auth database
68 68 """
69 69 self.check_for_db(override)
70 70 if override:
71 71 log.info("database exisist and it's going to be destroyed")
72 72 if self.db_exists:
73 73 os.remove(jn(ROOT, self.dbname))
74 74 Base.metadata.create_all(checkfirst=override)
75 75 log.info('Created tables for %s', self.dbname)
76 76
77 77 def admin_prompt(self):
78 78 import getpass
79 79 username = raw_input('Specify admin username:')
80 80 password = getpass.getpass('Specify admin password:')
81 81 self.create_user(username, password, True)
82
83 def config_prompt(self):
84 log.info('Seting up repositories.config')
82 85
86
87 path = raw_input('Specify valid full path to your repositories'
88 ' you can change this later application settings:')
89
90 if not os.path.isdir(path):
91 log.error('You entered wrong path')
92 sys.exit()
93
94 hooks = HgAppUi()
95 hooks.ui_section = 'hooks'
96 hooks.ui_key = 'changegroup'
97 hooks.ui_value = 'hg update >&2'
98
99 web1 = HgAppUi()
100 web1.ui_section = 'web'
101 web1.ui_key = 'push_ssl'
102 web1.ui_value = 'false'
103
104 web2 = HgAppUi()
105 web2.ui_section = 'web'
106 web2.ui_key = 'allow_archive'
107 web2.ui_value = 'gz zip bz2'
108
109 web3 = HgAppUi()
110 web3.ui_section = 'web'
111 web3.ui_key = 'allow_push'
112 web3.ui_value = '*'
113
114 web4 = HgAppUi()
115 web4.ui_section = 'web'
116 web4.ui_key = 'baseurl'
117 web4.ui_value = '/'
118
119 paths = HgAppUi()
120 paths.ui_section = 'paths'
121 paths.ui_key = '/'
122 paths.ui_value = os.path.join(path, '*')
123
124
125 try:
126 self.sa.add(hooks)
127 self.sa.add(web1)
128 self.sa.add(web2)
129 self.sa.add(web3)
130 self.sa.add(web4)
131 self.sa.add(paths)
132 self.sa.commit()
133 except:
134 self.sa.rollback()
135 raise
136 log.info('created ui config')
137
83 138 def create_user(self, username, password, admin=False):
84 139
85 140 log.info('creating default user')
86 141 #create default user for handling default permissions.
87 142 def_user = User()
88 143 def_user.username = 'default'
89 144 def_user.password = get_crypt_password(str(uuid.uuid1())[:8])
90 145 def_user.name = 'default'
91 146 def_user.lastname = 'default'
92 147 def_user.email = 'default@default.com'
93 148 def_user.admin = False
94 149 def_user.active = False
95 150
96 self.sa.add(def_user)
97
98 151 log.info('creating administrator user %s', username)
99 152 new_user = User()
100 153 new_user.username = username
101 154 new_user.password = get_crypt_password(password)
102 155 new_user.name = 'Hg'
103 156 new_user.lastname = 'Admin'
104 157 new_user.email = 'admin@localhost'
105 158 new_user.admin = admin
106 159 new_user.active = True
107 160
108 161 try:
162 self.sa.add(def_user)
109 163 self.sa.add(new_user)
110 164 self.sa.commit()
111 165 except:
112 166 self.sa.rollback()
113 167 raise
114 168
115 169 def create_permissions(self):
116 170 #module.(access|create|change|delete)_[name]
117 171 #module.(read|write|owner)
118 172 perms = [('repository.none', 'Repository no access'),
119 173 ('repository.read', 'Repository read access'),
120 174 ('repository.write', 'Repository write access'),
121 175 ('repository.admin', 'Repository admin access'),
122 176 ('hg.admin', 'Hg Administrator'),
123 177 ]
124 178
125 179 for p in perms:
126 180 new_perm = Permission()
127 181 new_perm.permission_name = p[0]
128 182 new_perm.permission_longname = p[1]
129 183 try:
130 184 self.sa.add(new_perm)
131 185 self.sa.commit()
132 186 except:
133 187 self.sa.rollback()
134 188 raise
@@ -1,225 +1,226 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # middleware to handle mercurial api calls
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20
21 21 """
22 22 Created on 2010-04-28
23 23
24 24 @author: marcink
25 25 SimpleHG middleware for handling mercurial protocol request (push/clone etc.)
26 26 It's implemented with basic auth function
27 27 """
28 28 from datetime import datetime
29 29 from itertools import chain
30 30 from mercurial.hgweb import hgweb
31 31 from mercurial.hgweb.request import wsgiapplication
32 32 from mercurial.error import RepoError
33 33 from paste.auth.basic import AuthBasicAuthenticator
34 34 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
35 35 from pylons_app.lib.auth import authfunc, HasPermissionAnyMiddleware
36 36 from pylons_app.lib.utils import is_mercurial, make_ui, invalidate_cache, \
37 37 check_repo_fast
38 38 from pylons_app.model import meta
39 39 from pylons_app.model.db import UserLog, User
40 40 import pylons_app.lib.helpers as h
41 41 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
42 42 import logging
43 43 import os
44 44 import traceback
45 45 log = logging.getLogger(__name__)
46 46
47 47 class SimpleHg(object):
48 48
49 49 def __init__(self, application, config):
50 50 self.application = application
51 51 self.config = config
52 52 #authenticate this mercurial request using
53 realm = '%s %s' % (self.config['hg_app_name'], 'mercurial repository')
53 realm = self.config['hg_app_auth_realm']
54 54 self.authenticate = AuthBasicAuthenticator(realm, authfunc)
55 55
56 56 def __call__(self, environ, start_response):
57 57 if not is_mercurial(environ):
58 58 return self.application(environ, start_response)
59 59 else:
60 60 #===================================================================
61 61 # AUTHENTICATE THIS MERCURIAL REQUEST
62 62 #===================================================================
63 63 username = REMOTE_USER(environ)
64 64 if not username:
65 65 result = self.authenticate(environ)
66 66 if isinstance(result, str):
67 67 AUTH_TYPE.update(environ, 'basic')
68 68 REMOTE_USER.update(environ, result)
69 69 else:
70 70 return result.wsgi_application(environ, start_response)
71 71
72 72 try:
73 73 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
74 74 except:
75 75 log.error(traceback.format_exc())
76 76 return HTTPInternalServerError()(environ, start_response)
77 77
78 78 #===================================================================
79 79 # CHECK PERMISSIONS FOR THIS REQUEST
80 80 #===================================================================
81 81 action = self.__get_action(environ)
82 82 if action:
83 83 username = self.__get_environ_user(environ)
84 84 try:
85 85 sa = meta.Session
86 86 user = sa.query(User)\
87 87 .filter(User.username == username).one()
88 88 except:
89 89 log.error(traceback.format_exc())
90 90 return HTTPInternalServerError()(environ, start_response)
91 91 #check permissions for this repository
92 92 if action == 'pull':
93 93 if not HasPermissionAnyMiddleware('repository.read',
94 94 'repository.write',
95 95 'repository.admin')\
96 96 (user, repo_name):
97 97 return HTTPForbidden()(environ, start_response)
98 98 if action == 'push':
99 99 if not HasPermissionAnyMiddleware('repository.write',
100 100 'repository.admin')\
101 101 (user, repo_name):
102 102 return HTTPForbidden()(environ, start_response)
103 103
104 104 #log action
105 105 proxy_key = 'HTTP_X_REAL_IP'
106 106 def_key = 'REMOTE_ADDR'
107 107 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
108 108 self.__log_user_action(user, action, repo_name, ipaddr)
109 109
110 110 #===================================================================
111 111 # MERCURIAL REQUEST HANDLING
112 112 #===================================================================
113 113 environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path
114 self.baseui = make_ui(self.config['hg_app_repo_conf'])
114 self.baseui = make_ui('db')
115 115 self.basepath = self.config['base_path']
116 116 self.repo_path = os.path.join(self.basepath, repo_name)
117 117
118 118 #quick check if that dir exists...
119 119 if check_repo_fast(repo_name, self.basepath):
120 120 return HTTPNotFound()(environ, start_response)
121
122 121 try:
123 122 app = wsgiapplication(self.__make_app)
124 123 except RepoError as e:
125 124 if str(e).find('not found') != -1:
126 125 return HTTPNotFound()(environ, start_response)
127 126 except Exception:
128 127 log.error(traceback.format_exc())
129 128 return HTTPInternalServerError()(environ, start_response)
130 129
131 130 #invalidate cache on push
132 131 if action == 'push':
133 132 self.__invalidate_cache(repo_name)
134 133 messages = []
135 134 messages.append('thank you for using hg-app')
136 135
137 136 return self.msg_wrapper(app, environ, start_response, messages)
138 137 else:
139 138 return app(environ, start_response)
140 139
141 140
142 141 def msg_wrapper(self, app, environ, start_response, messages=[]):
143 142 """
144 143 Wrapper for custom messages that come out of mercurial respond messages
145 144 is a list of messages that the user will see at the end of response
146 145 from merurial protocol actions that involves remote answers
147 146 @param app:
148 147 @param environ:
149 148 @param start_response:
150 149 """
151 150 def custom_messages(msg_list):
152 151 for msg in msg_list:
153 152 yield msg + '\n'
154 153 org_response = app(environ, start_response)
155 154 return chain(org_response, custom_messages(messages))
156 155
157 156 def __make_app(self):
158 hgserve = hgweb(self.repo_path)
157 hgserve = hgweb(str(self.repo_path), baseui=self.baseui)
159 158 return self.__load_web_settings(hgserve)
160 159
161 160 def __get_environ_user(self, environ):
162 161 return environ.get('REMOTE_USER')
163 162
164 163 def __get_size(self, repo_path, content_size):
165 164 size = int(content_size)
166 165 for path, dirs, files in os.walk(repo_path):
167 166 if path.find('.hg') == -1:
168 167 for f in files:
169 168 size += os.path.getsize(os.path.join(path, f))
170 169 return size
171 170 return h.format_byte_size(size)
172 171
173 172 def __get_action(self, environ):
174 173 """
175 174 Maps mercurial request commands into a pull or push command.
176 175 @param environ:
177 176 """
178 177 mapping = {'changegroup': 'pull',
179 178 'changegroupsubset': 'pull',
180 179 'stream_out': 'pull',
181 180 'listkeys': 'pull',
182 181 'unbundle': 'push',
183 182 'pushkey': 'push', }
184 183
185 184 for qry in environ['QUERY_STRING'].split('&'):
186 185 if qry.startswith('cmd'):
187 186 cmd = qry.split('=')[-1]
188 187 if mapping.has_key(cmd):
189 188 return mapping[cmd]
190 189
191 190 def __log_user_action(self, user, action, repo, ipaddr):
192 191 sa = meta.Session
193 192 try:
194 193 user_log = UserLog()
195 194 user_log.user_id = user.user_id
196 195 user_log.action = action
197 196 user_log.repository = repo.replace('/', '')
198 197 user_log.action_date = datetime.now()
199 198 user_log.user_ip = ipaddr
200 199 sa.add(user_log)
201 200 sa.commit()
202 201 log.info('Adding user %s, action %s on %s',
203 202 user.username, action, repo)
204 203 except Exception as e:
205 204 sa.rollback()
206 205 log.error('could not log user action:%s', str(e))
207 206
208 207 def __invalidate_cache(self, repo_name):
209 208 """we know that some change was made to repositories and we should
210 209 invalidate the cache to see the changes right away but only for
211 210 push requests"""
212 211 invalidate_cache('cached_repo_list')
213 212 invalidate_cache('full_changelog', repo_name)
214 213
215 214
216 215 def __load_web_settings(self, hgserve):
217 repoui = make_ui(os.path.join(self.repo_path, '.hg', 'hgrc'), False)
218 216 #set the global ui for hgserve
219 217 hgserve.repo.ui = self.baseui
220 218
219 hgrc = os.path.join(self.repo_path, '.hg', 'hgrc')
220 repoui = make_ui('file', hgrc, False)
221
221 222 if repoui:
222 223 #set the repository based config
223 224 hgserve.repo.ui = repoui
224 225
225 226 return hgserve
@@ -1,184 +1,189 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # Utilities for hg app
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 # This program is free software; you can redistribute it and/or
6 6 # modify it under the terms of the GNU General Public License
7 7 # as published by the Free Software Foundation; version 2
8 8 # of the License or (at your opinion) any later version of the license.
9 9 #
10 10 # This program is distributed in the hope that it will be useful,
11 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 # GNU General Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License
16 16 # along with this program; if not, write to the Free Software
17 17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 18 # MA 02110-1301, USA.
19 19
20 20 """
21 21 Created on April 18, 2010
22 22 Utilities for hg app
23 23 @author: marcink
24 24 """
25 25
26 26 import os
27 27 import logging
28 28 from mercurial import ui, config, hg
29 29 from mercurial.error import RepoError
30 from pylons_app.model.db import Repository, User
30 from pylons_app.model.db import Repository, User, HgAppUi
31 31 log = logging.getLogger(__name__)
32 32
33 33
34 34 def get_repo_slug(request):
35 35 return request.environ['pylons.routes_dict'].get('repo_name')
36 36
37 37 def is_mercurial(environ):
38 38 """
39 39 Returns True if request's target is mercurial server - header
40 40 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
41 41 """
42 42 http_accept = environ.get('HTTP_ACCEPT')
43 43 if http_accept and http_accept.startswith('application/mercurial'):
44 44 return True
45 45 return False
46 46
47 47 def check_repo_dir(paths):
48 48 repos_path = paths[0][1].split('/')
49 49 if repos_path[-1] in ['*', '**']:
50 50 repos_path = repos_path[:-1]
51 51 if repos_path[0] != '/':
52 52 repos_path[0] = '/'
53 53 if not os.path.isdir(os.path.join(*repos_path)):
54 54 raise Exception('Not a valid repository in %s' % paths[0][1])
55 55
56 56 def check_repo_fast(repo_name, base_path):
57 57 if os.path.isdir(os.path.join(base_path, repo_name)):return False
58 58 return True
59 59
60 60 def check_repo(repo_name, base_path, verify=True):
61 61
62 62 repo_path = os.path.join(base_path, repo_name)
63 63
64 64 try:
65 65 if not check_repo_fast(repo_name, base_path):
66 66 return False
67 67 r = hg.repository(ui.ui(), repo_path)
68 68 if verify:
69 69 hg.verify(r)
70 70 #here we hnow that repo exists it was verified
71 71 log.info('%s repo is already created', repo_name)
72 72 return False
73 73 except RepoError:
74 74 #it means that there is no valid repo there...
75 75 log.info('%s repo is free for creation', repo_name)
76 76 return True
77 77
78 def make_ui(path=None, checkpaths=True):
78 def make_ui(read_from='file', path=None, checkpaths=True):
79 79 """
80 A funcion that will read python rc files and make an ui from read options
80 A function that will read python rc files or database
81 and make an mercurial ui object from read options
81 82
82 83 @param path: path to mercurial config file
84 @param checkpaths: check the path
85 @param read_from: read from 'file' or 'db'
83 86 """
84 if not path:
85 log.error('repos config path is empty !')
86
87 if not os.path.isfile(path):
88 log.warning('Unable to read config file %s' % path)
89 return False
90 87 #propagated from mercurial documentation
91 sections = [
92 'alias',
93 'auth',
94 'decode/encode',
95 'defaults',
96 'diff',
97 'email',
98 'extensions',
99 'format',
100 'merge-patterns',
101 'merge-tools',
102 'hooks',
103 'http_proxy',
104 'smtp',
105 'patch',
106 'paths',
107 'profiling',
108 'server',
109 'trusted',
110 'ui',
111 'web',
112 ]
88 sections = ['alias', 'auth',
89 'decode/encode', 'defaults',
90 'diff', 'email',
91 'extensions', 'format',
92 'merge-patterns', 'merge-tools',
93 'hooks', 'http_proxy',
94 'smtp', 'patch',
95 'paths', 'profiling',
96 'server', 'trusted',
97 'ui', 'web', ]
98 baseui = ui.ui()
113 99
114 baseui = ui.ui()
115 cfg = config.config()
116 cfg.read(path)
117 if checkpaths:check_repo_dir(cfg.items('paths'))
118
119 for section in sections:
120 for k, v in cfg.items(section):
121 baseui.setconfig(section, k, v)
100
101 if read_from == 'file':
102 if not os.path.isfile(path):
103 log.warning('Unable to read config file %s' % path)
104 return False
105
106 cfg = config.config()
107 cfg.read(path)
108 for section in sections:
109 for k, v in cfg.items(section):
110 baseui.setconfig(section, k, v)
111 if checkpaths:check_repo_dir(cfg.items('paths'))
112
113
114 elif read_from == 'db':
115 from pylons_app.model.meta import Session
116 sa = Session()
117
118 hg_ui = sa.query(HgAppUi).all()
119 for ui_ in hg_ui:
120 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
121
122 122
123 123 return baseui
124 124
125
126 def set_hg_app_config(config):
127 config['hg_app_auth_realm'] = 'realm'
128 config['hg_app_name'] = 'app name'
129
125 130 def invalidate_cache(name, *args):
126 131 """Invalidates given name cache"""
127 132
128 133 from beaker.cache import region_invalidate
129 134 log.info('INVALIDATING CACHE FOR %s', name)
130 135
131 136 """propagate our arguments to make sure invalidation works. First
132 137 argument has to be the name of cached func name give to cache decorator
133 138 without that the invalidation would not work"""
134 139 tmp = [name]
135 140 tmp.extend(args)
136 141 args = tuple(tmp)
137 142
138 143 if name == 'cached_repo_list':
139 144 from pylons_app.model.hg_model import _get_repos_cached
140 145 region_invalidate(_get_repos_cached, None, *args)
141 146
142 147 if name == 'full_changelog':
143 148 from pylons_app.model.hg_model import _full_changelog_cached
144 149 region_invalidate(_full_changelog_cached, None, *args)
145 150
146 151 from vcs.backends.base import BaseChangeset
147 152 from vcs.utils.lazy import LazyProperty
148 153 class EmptyChangeset(BaseChangeset):
149 154
150 155 revision = -1
151 156 message = ''
152 157
153 158 @LazyProperty
154 159 def raw_id(self):
155 160 """
156 161 Returns raw string identifing this changeset, useful for web
157 162 representation.
158 163 """
159 164 return '0' * 12
160 165
161 166
162 167 def repo2db_mapper(initial_repo_list):
163 168 """
164 169 maps all found repositories into db
165 170 """
166 171 from pylons_app.model.meta import Session
167 172 from pylons_app.model.repo_model import RepoModel
168 173
169 174 sa = Session()
170 175 user = sa.query(User).filter(User.admin == True).first()
171 176
172 177 rm = RepoModel()
173 178
174 179 for name, repo in initial_repo_list.items():
175 180 if not sa.query(Repository).get(name):
176 181 log.info('repository %s not found creating default', name)
177 182
178 183 form_data = {
179 184 'repo_name':name,
180 185 'description':repo.description if repo.description != 'unknown' else \
181 186 'auto description for %s' % name,
182 187 'private':False
183 188 }
184 189 rm.create(form_data, user, just_db=True)
@@ -1,71 +1,86 b''
1 1 from pylons_app.model.meta import Base
2 2 from sqlalchemy.orm import relation, backref
3 3 from sqlalchemy import *
4 4 from vcs.utils.lazy import LazyProperty
5 5
6 class HgAppSettings(Base):
7 __tablename__ = 'hg_app_settings'
8 __table_args__ = {'useexisting':True}
9 app_settings_id = Column("app_settings_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
10 app_title = Column("app_title", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
11 app_auth_realm = Column("auth_realm", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
12
13 class HgAppUi(Base):
14 __tablename__ = 'hg_app_ui'
15 __table_args__ = {'useexisting':True}
16 ui_id = Column("ui_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
17 ui_section = Column("ui_section", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
18 ui_key = Column("ui_key", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
19 ui_value = Column("ui_value", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
20
6 21 class User(Base):
7 22 __tablename__ = 'users'
8 23 __table_args__ = {'useexisting':True}
9 24 user_id = Column("user_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
10 25 username = Column("username", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
11 26 password = Column("password", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
12 27 active = Column("active", BOOLEAN(), nullable=True, unique=None, default=None)
13 28 admin = Column("admin", BOOLEAN(), nullable=True, unique=None, default=False)
14 29 name = Column("name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
15 30 lastname = Column("lastname", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
16 31 email = Column("email", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
17 32 last_login = Column("last_login", DATETIME(timezone=False), nullable=True, unique=None, default=None)
18 33
19 34 user_log = relation('UserLog')
20 35
21 36 @LazyProperty
22 37 def full_contact(self):
23 38 return '%s %s <%s>' % (self.name, self.lastname, self.email)
24 39
25 40 def __repr__(self):
26 41 return "<User('%s:%s')>" % (self.user_id, self.username)
27 42
28 43 class UserLog(Base):
29 44 __tablename__ = 'user_logs'
30 45 __table_args__ = {'useexisting':True}
31 46 user_log_id = Column("user_log_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
32 47 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
33 48 user_ip = Column("user_ip", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
34 49 repository = Column("repository", TEXT(length=None, convert_unicode=False, assert_unicode=None), ForeignKey(u'repositories.repo_name'), nullable=False, unique=None, default=None)
35 50 action = Column("action", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
36 51 action_date = Column("action_date", DATETIME(timezone=False), nullable=True, unique=None, default=None)
37 52
38 53 user = relation('User')
39 54
40 55 class Repository(Base):
41 56 __tablename__ = 'repositories'
42 57 __table_args__ = {'useexisting':True}
43 58 repo_name = Column("repo_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None, primary_key=True)
44 59 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=False, default=None)
45 60 private = Column("private", BOOLEAN(), nullable=True, unique=None, default=None)
46 61 description = Column("description", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
47 62
48 63 user = relation('User')
49 64 repo2perm = relation('Repo2Perm', cascade='all')
50 65
51 66 class Permission(Base):
52 67 __tablename__ = 'permissions'
53 68 __table_args__ = {'useexisting':True}
54 69 permission_id = Column("permission_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
55 70 permission_name = Column("permission_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
56 71 permission_longname = Column("permission_longname", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
57 72
58 73 def __repr__(self):
59 74 return "<Permission('%s:%s')>" % (self.permission_id, self.permission_name)
60 75
61 76 class Repo2Perm(Base):
62 77 __tablename__ = 'repo_to_perm'
63 78 __table_args__ = (UniqueConstraint('user_id', 'repository'), {'useexisting':True})
64 79 repo2perm_id = Column("repo2perm_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
65 80 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
66 81 permission_id = Column("permission_id", INTEGER(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None)
67 82 repository = Column("repository", TEXT(length=None, convert_unicode=False, assert_unicode=None), ForeignKey(u'repositories.repo_name'), nullable=False, unique=None, default=None)
68 83
69 84 user = relation('User')
70 85 permission = relation('Permission')
71 86
@@ -1,49 +1,23 b''
1 1 """Setup the pylons_app application"""
2 2
3 3 from os.path import dirname as dn, join as jn
4 4 from pylons_app.config.environment import load_environment
5 5 from pylons_app.lib.db_manage import DbManage
6 6 import logging
7 7 import os
8 8 import sys
9 9
10 10 log = logging.getLogger(__name__)
11 11
12 12 ROOT = dn(dn(os.path.realpath(__file__)))
13 13 sys.path.append(ROOT)
14 14
15
16 def setup_repository():
17 log.info('Seting up repositories.config')
18 fname = 'repositories.config'
19
20 try:
21 tmpl = open(jn(ROOT, 'pylons_app', 'config', 'repositories.config_tmpl')).read()
22 except IOError:
23 raise
24
25 path = raw_input('Specify valid full path to your repositories'
26 ' you can change this later in repositories.config file:')
27
28 if not os.path.isdir(path):
29 log.error('You entered wrong path')
30 sys.exit()
31
32
33 path = jn(path, '*')
34 dest_path = jn(ROOT, fname)
35 f = open(dest_path, 'wb')
36 f.write(tmpl % {'repo_location':path})
37 f.close()
38 log.info('created repositories.config in %s', dest_path)
39
40
41 15 def setup_app(command, conf, vars):
42 16 """Place any commands to setup pylons_app here"""
43 setup_repository()
44 17 dbmanage = DbManage(log_sql=True)
45 18 dbmanage.create_tables(override=True)
19 dbmanage.config_prompt()
46 20 dbmanage.admin_prompt()
47 21 dbmanage.create_permissions()
48 22 load_environment(conf.global_conf, conf.local_conf)
49 23
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now