##// END OF EJS Templates
always use json from compat module
marcink -
r3705:f37d7514 beta
parent child Browse files
Show More
@@ -1,237 +1,244
1 1 # This program is free software: you can redistribute it and/or modify
2 2 # it under the terms of the GNU General Public License as published by
3 3 # the Free Software Foundation, either version 3 of the License, or
4 4 # (at your option) any later version.
5 5 #
6 6 # This program is distributed in the hope that it will be useful,
7 7 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 8 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 9 # GNU General Public License for more details.
10 10 #
11 11 # You should have received a copy of the GNU General Public License
12 12 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 13
14 14 import ldap
15 15 import urllib2
16 16 import uuid
17
18 try:
19 from rhodecode.lib.compat import json
20 except ImportError:
21 try:
22 import simplejson as json
23 except ImportError:
17 24 import json
18 25
19 26 from ConfigParser import ConfigParser
20 27
21 28 config = ConfigParser()
22 29 config.read('ldap_sync.conf')
23 30
24 31
25 32 class InvalidResponseIDError(Exception):
26 33 """ Request and response don't have the same UUID. """
27 34
28 35
29 36 class RhodecodeResponseError(Exception):
30 37 """ Response has an error, something went wrong with request execution. """
31 38
32 39
33 40 class UserAlreadyInGroupError(Exception):
34 41 """ User is already a member of the target group. """
35 42
36 43
37 44 class UserNotInGroupError(Exception):
38 45 """ User is not a member of the target group. """
39 46
40 47
41 48 class RhodecodeAPI():
42 49
43 50 def __init__(self, url, key):
44 51 self.url = url
45 52 self.key = key
46 53
47 54 def get_api_data(self, uid, method, args):
48 55 """Prepare dict for API post."""
49 56 return {
50 57 "id": uid,
51 58 "api_key": self.key,
52 59 "method": method,
53 60 "args": args
54 61 }
55 62
56 63 def rhodecode_api_post(self, method, args):
57 64 """Send a generic API post to Rhodecode.
58 65
59 66 This will generate the UUID for validation check after the
60 67 response is returned. Handle errors and get the result back.
61 68 """
62 69 uid = str(uuid.uuid1())
63 70 data = self.get_api_data(uid, method, args)
64 71
65 72 data = json.dumps(data)
66 73 headers = {'content-type': 'text/plain'}
67 74 req = urllib2.Request(self.url, data, headers)
68 75
69 76 response = urllib2.urlopen(req)
70 77 response = json.load(response)
71 78
72 79 if uid != response["id"]:
73 80 raise InvalidResponseIDError("UUID does not match.")
74 81
75 82 if response["error"] != None:
76 83 raise RhodecodeResponseError(response["error"])
77 84
78 85 return response["result"]
79 86
80 87 def create_group(self, name, active=True):
81 88 """Create the Rhodecode user group."""
82 89 args = {
83 90 "group_name": name,
84 91 "active": str(active)
85 92 }
86 93 self.rhodecode_api_post("create_users_group", args)
87 94
88 95 def add_membership(self, group, username):
89 96 """Add specific user to a group."""
90 97 args = {
91 98 "usersgroupid": group,
92 99 "userid": username
93 100 }
94 101 result = self.rhodecode_api_post("add_user_to_users_group", args)
95 102 if not result["success"]:
96 103 raise UserAlreadyInGroupError("User %s already in group %s." %
97 104 (username, group))
98 105
99 106 def remove_membership(self, group, username):
100 107 """Remove specific user from a group."""
101 108 args = {
102 109 "usersgroupid": group,
103 110 "userid": username
104 111 }
105 112 result = self.rhodecode_api_post("remove_user_from_users_group", args)
106 113 if not result["success"]:
107 114 raise UserNotInGroupError("User %s not in group %s." %
108 115 (username, group))
109 116
110 117 def get_group_members(self, name):
111 118 """Get the list of member usernames from a user group."""
112 119 args = {"usersgroupid": name}
113 120 members = self.rhodecode_api_post("get_users_group", args)['members']
114 121 member_list = []
115 122 for member in members:
116 123 member_list.append(member["username"])
117 124 return member_list
118 125
119 126 def get_group(self, name):
120 127 """Return group info."""
121 128 args = {"usersgroupid": name}
122 129 return self.rhodecode_api_post("get_users_group", args)
123 130
124 131 def get_user(self, username):
125 132 """Return user info."""
126 133 args = {"userid": username}
127 134 return self.rhodecode_api_post("get_user", args)
128 135
129 136
130 137 class LdapClient():
131 138
132 139 def __init__(self, uri, user, key, base_dn):
133 140 self.client = ldap.initialize(uri, trace_level=0)
134 141 self.client.set_option(ldap.OPT_REFERRALS, 0)
135 142 self.client.simple_bind(user, key)
136 143 self.base_dn = base_dn
137 144
138 145 def __del__(self):
139 146 self.client.unbind()
140 147
141 148 def get_groups(self):
142 149 """Get all the groups in form of dict {group_name: group_info,...}."""
143 150 searchFilter = "objectClass=groupOfUniqueNames"
144 151 result = self.client.search_s(self.base_dn, ldap.SCOPE_SUBTREE,
145 152 searchFilter)
146 153
147 154 groups = {}
148 155 for group in result:
149 156 groups[group[1]['cn'][0]] = group[1]
150 157
151 158 return groups
152 159
153 160 def get_group_users(self, groups, group):
154 161 """Returns all the users belonging to a single group.
155 162
156 163 Based on the list of groups and memberships, returns all the
157 164 users belonging to a single group, searching recursively.
158 165 """
159 166 users = []
160 167 for member in groups[group]["uniqueMember"]:
161 168 member = self.parse_member_string(member)
162 169 if member[0] == "uid":
163 170 users.append(member[1])
164 171 elif member[0] == "cn":
165 172 users += self.get_group_users(groups, member[1])
166 173
167 174 return users
168 175
169 176 def parse_member_string(self, member):
170 177 """Parses the member string and returns a touple of type and name.
171 178
172 179 Unique member can be either user or group. Users will have 'uid' as
173 180 prefix while groups will have 'cn'.
174 181 """
175 182 member = member.split(",")[0]
176 183 return member.split('=')
177 184
178 185
179 186 class LdapSync(object):
180 187
181 188 def __init__(self):
182 189 self.ldap_client = LdapClient(config.get("default", "ldap_uri"),
183 190 config.get("default", "ldap_user"),
184 191 config.get("default", "ldap_key"),
185 192 config.get("default", "base_dn"))
186 193 self.rhodocode_api = RhodecodeAPI(config.get("default", "api_url"),
187 194 config.get("default", "api_key"))
188 195
189 196 def update_groups_from_ldap(self):
190 197 """Add all the groups from LDAP to Rhodecode."""
191 198 added = existing = 0
192 199 groups = self.ldap_client.get_groups()
193 200 for group in groups:
194 201 try:
195 202 self.rhodecode_api.create_group(group)
196 203 added += 1
197 204 except Exception:
198 205 existing += 1
199 206
200 207 return added, existing
201 208
202 209 def update_memberships_from_ldap(self, group):
203 210 """Update memberships in rhodecode based on the LDAP groups."""
204 211 groups = self.ldap_client.get_groups()
205 212 group_users = self.ldap_client.get_group_users(groups, group)
206 213
207 214 # Delete memberships first from each group which are not part
208 215 # of the group any more.
209 216 rhodecode_members = self.rhodecode_api.get_group_members(group)
210 217 for rhodecode_member in rhodecode_members:
211 218 if rhodecode_member not in group_users:
212 219 try:
213 220 self.rhodocode_api.remove_membership(group,
214 221 rhodecode_member)
215 222 except UserNotInGroupError:
216 223 pass
217 224
218 225 # Add memberships.
219 226 for member in group_users:
220 227 try:
221 228 self.rhodecode_api.add_membership(group, member)
222 229 except UserAlreadyInGroupError:
223 230 # TODO: handle somehow maybe..
224 231 pass
225 232
226 233
227 234 if __name__ == '__main__':
228 235 sync = LdapSync()
229 236 print sync.update_groups_from_ldap()
230 237
231 238 for gr in sync.ldap_client.get_groups():
232 239 # TODO: exception when user does not exist during add membership...
233 240 # How should we handle this.. Either sync users as well at this step,
234 241 # or just ignore those who don't exist. If we want the second case,
235 242 # we need to find a way to recognize the right exception (we always get
236 243 # RhodecodeResponseError with no error code so maybe by return msg (?)
237 244 sync.update_memberships_from_ldap(gr)
@@ -1,392 +1,392
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.repos_groups
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Repository groups controller for RhodeCode
7 7
8 8 :created_on: Mar 23, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29
30 30 from formencode import htmlfill
31 31
32 32 from pylons import request, tmpl_context as c, url
33 33 from pylons.controllers.util import abort, redirect
34 34 from pylons.i18n.translation import _
35 35
36 36 from sqlalchemy.exc import IntegrityError
37 37
38 38 import rhodecode
39 39 from rhodecode.lib import helpers as h
40 from rhodecode.lib.ext_json import json
40 from rhodecode.lib.compat import json
41 41 from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator,\
42 42 HasReposGroupPermissionAnyDecorator, HasReposGroupPermissionAll,\
43 43 HasPermissionAll
44 44 from rhodecode.lib.base import BaseController, render
45 45 from rhodecode.model.db import RepoGroup, Repository
46 46 from rhodecode.model.repos_group import ReposGroupModel
47 47 from rhodecode.model.forms import ReposGroupForm
48 48 from rhodecode.model.meta import Session
49 49 from rhodecode.model.repo import RepoModel
50 50 from webob.exc import HTTPInternalServerError, HTTPNotFound
51 51 from rhodecode.lib.utils2 import str2bool, safe_int
52 52 from sqlalchemy.sql.expression import func
53 53 from rhodecode.model.scm import GroupList
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 class ReposGroupsController(BaseController):
59 59 """REST Controller styled on the Atom Publishing Protocol"""
60 60 # To properly map this controller, ensure your config/routing.py
61 61 # file has a resource setup:
62 62 # map.resource('repos_group', 'repos_groups')
63 63
64 64 @LoginRequired()
65 65 def __before__(self):
66 66 super(ReposGroupsController, self).__before__()
67 67
68 68 def __load_defaults(self, allow_empty_group=False, exclude_group_ids=[]):
69 69 if HasPermissionAll('hg.admin')('group edit'):
70 70 #we're global admin, we're ok and we can create TOP level groups
71 71 allow_empty_group = True
72 72
73 73 #override the choices for this form, we need to filter choices
74 74 #and display only those we have ADMIN right
75 75 groups_with_admin_rights = GroupList(RepoGroup.query().all(),
76 76 perm_set=['group.admin'])
77 77 c.repo_groups = RepoGroup.groups_choices(groups=groups_with_admin_rights,
78 78 show_empty_group=allow_empty_group)
79 79 # exclude filtered ids
80 80 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
81 81 c.repo_groups)
82 82 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
83 83 repo_model = RepoModel()
84 84 c.users_array = repo_model.get_users_js()
85 85 c.users_groups_array = repo_model.get_users_groups_js()
86 86
87 87 def __load_data(self, group_id):
88 88 """
89 89 Load defaults settings for edit, and update
90 90
91 91 :param group_id:
92 92 """
93 93 repo_group = RepoGroup.get_or_404(group_id)
94 94 data = repo_group.get_dict()
95 95 data['group_name'] = repo_group.name
96 96
97 97 # fill repository users
98 98 for p in repo_group.repo_group_to_perm:
99 99 data.update({'u_perm_%s' % p.user.username:
100 100 p.permission.permission_name})
101 101
102 102 # fill repository groups
103 103 for p in repo_group.users_group_to_perm:
104 104 data.update({'g_perm_%s' % p.users_group.users_group_name:
105 105 p.permission.permission_name})
106 106
107 107 return data
108 108
109 109 def _revoke_perms_on_yourself(self, form_result):
110 110 _up = filter(lambda u: c.rhodecode_user.username == u[0],
111 111 form_result['perms_updates'])
112 112 _new = filter(lambda u: c.rhodecode_user.username == u[0],
113 113 form_result['perms_new'])
114 114 if _new and _new[0][1] != 'group.admin' or _up and _up[0][1] != 'group.admin':
115 115 return True
116 116 return False
117 117
118 118 def index(self, format='html'):
119 119 """GET /repos_groups: All items in the collection"""
120 120 # url('repos_groups')
121 121 group_iter = GroupList(RepoGroup.query().all(), perm_set=['group.admin'])
122 122 sk = lambda g: g.parents[0].group_name if g.parents else g.group_name
123 123 c.groups = sorted(group_iter, key=sk)
124 124 return render('admin/repos_groups/repos_groups_show.html')
125 125
126 126 def create(self):
127 127 """POST /repos_groups: Create a new item"""
128 128 # url('repos_groups')
129 129
130 130 self.__load_defaults()
131 131
132 132 # permissions for can create group based on parent_id are checked
133 133 # here in the Form
134 134 repos_group_form = ReposGroupForm(available_groups=
135 135 map(lambda k: unicode(k[0]), c.repo_groups))()
136 136 try:
137 137 form_result = repos_group_form.to_python(dict(request.POST))
138 138 ReposGroupModel().create(
139 139 group_name=form_result['group_name'],
140 140 group_description=form_result['group_description'],
141 141 parent=form_result['group_parent_id'],
142 142 owner=self.rhodecode_user.user_id
143 143 )
144 144 Session().commit()
145 145 h.flash(_('Created repository group %s') \
146 146 % form_result['group_name'], category='success')
147 147 #TODO: in futureaction_logger(, '', '', '', self.sa)
148 148 except formencode.Invalid, errors:
149 149 return htmlfill.render(
150 150 render('admin/repos_groups/repos_groups_add.html'),
151 151 defaults=errors.value,
152 152 errors=errors.error_dict or {},
153 153 prefix_error=False,
154 154 encoding="UTF-8")
155 155 except Exception:
156 156 log.error(traceback.format_exc())
157 157 h.flash(_('Error occurred during creation of repository group %s') \
158 158 % request.POST.get('group_name'), category='error')
159 159 parent_group_id = form_result['group_parent_id']
160 160 #TODO: maybe we should get back to the main view, not the admin one
161 161 return redirect(url('repos_groups', parent_group=parent_group_id))
162 162
163 163 def new(self, format='html'):
164 164 """GET /repos_groups/new: Form to create a new item"""
165 165 # url('new_repos_group')
166 166 if HasPermissionAll('hg.admin')('group create'):
167 167 #we're global admin, we're ok and we can create TOP level groups
168 168 pass
169 169 else:
170 170 # we pass in parent group into creation form, thus we know
171 171 # what would be the group, we can check perms here !
172 172 group_id = safe_int(request.GET.get('parent_group'))
173 173 group = RepoGroup.get(group_id) if group_id else None
174 174 group_name = group.group_name if group else None
175 175 if HasReposGroupPermissionAll('group.admin')(group_name, 'group create'):
176 176 pass
177 177 else:
178 178 return abort(403)
179 179
180 180 self.__load_defaults()
181 181 return render('admin/repos_groups/repos_groups_add.html')
182 182
183 183 @HasReposGroupPermissionAnyDecorator('group.admin')
184 184 def update(self, group_name):
185 185 """PUT /repos_groups/group_name: Update an existing item"""
186 186 # Forms posted to this method should contain a hidden field:
187 187 # <input type="hidden" name="_method" value="PUT" />
188 188 # Or using helpers:
189 189 # h.form(url('repos_group', group_name=GROUP_NAME),
190 190 # method='put')
191 191 # url('repos_group', group_name=GROUP_NAME)
192 192
193 193 c.repos_group = ReposGroupModel()._get_repos_group(group_name)
194 194 if HasPermissionAll('hg.admin')('group edit'):
195 195 #we're global admin, we're ok and we can create TOP level groups
196 196 allow_empty_group = True
197 197 elif not c.repos_group.parent_group:
198 198 allow_empty_group = True
199 199 else:
200 200 allow_empty_group = False
201 201 self.__load_defaults(allow_empty_group=allow_empty_group,
202 202 exclude_group_ids=[c.repos_group.group_id])
203 203
204 204 repos_group_form = ReposGroupForm(
205 205 edit=True,
206 206 old_data=c.repos_group.get_dict(),
207 207 available_groups=c.repo_groups_choices,
208 208 can_create_in_root=allow_empty_group,
209 209 )()
210 210 try:
211 211 form_result = repos_group_form.to_python(dict(request.POST))
212 212 if not c.rhodecode_user.is_admin:
213 213 if self._revoke_perms_on_yourself(form_result):
214 214 msg = _('Cannot revoke permission for yourself as admin')
215 215 h.flash(msg, category='warning')
216 216 raise Exception('revoke admin permission on self')
217 217
218 218 new_gr = ReposGroupModel().update(group_name, form_result)
219 219 Session().commit()
220 220 h.flash(_('Updated repository group %s') \
221 221 % form_result['group_name'], category='success')
222 222 # we now have new name !
223 223 group_name = new_gr.group_name
224 224 #TODO: in future action_logger(, '', '', '', self.sa)
225 225 except formencode.Invalid, errors:
226 226
227 227 return htmlfill.render(
228 228 render('admin/repos_groups/repos_groups_edit.html'),
229 229 defaults=errors.value,
230 230 errors=errors.error_dict or {},
231 231 prefix_error=False,
232 232 encoding="UTF-8")
233 233 except Exception:
234 234 log.error(traceback.format_exc())
235 235 h.flash(_('Error occurred during update of repository group %s') \
236 236 % request.POST.get('group_name'), category='error')
237 237
238 238 return redirect(url('edit_repos_group', group_name=group_name))
239 239
240 240 @HasReposGroupPermissionAnyDecorator('group.admin')
241 241 def delete(self, group_name):
242 242 """DELETE /repos_groups/group_name: Delete an existing item"""
243 243 # Forms posted to this method should contain a hidden field:
244 244 # <input type="hidden" name="_method" value="DELETE" />
245 245 # Or using helpers:
246 246 # h.form(url('repos_group', group_name=GROUP_NAME),
247 247 # method='delete')
248 248 # url('repos_group', group_name=GROUP_NAME)
249 249
250 250 gr = c.repos_group = ReposGroupModel()._get_repos_group(group_name)
251 251 repos = gr.repositories.all()
252 252 if repos:
253 253 h.flash(_('This group contains %s repositores and cannot be '
254 254 'deleted') % len(repos), category='warning')
255 255 return redirect(url('repos_groups'))
256 256
257 257 children = gr.children.all()
258 258 if children:
259 259 h.flash(_('This group contains %s subgroups and cannot be deleted'
260 260 % (len(children))), category='warning')
261 261 return redirect(url('repos_groups'))
262 262
263 263 try:
264 264 ReposGroupModel().delete(group_name)
265 265 Session().commit()
266 266 h.flash(_('Removed repository group %s') % group_name,
267 267 category='success')
268 268 #TODO: in future action_logger(, '', '', '', self.sa)
269 269 except Exception:
270 270 log.error(traceback.format_exc())
271 271 h.flash(_('Error occurred during deletion of repos '
272 272 'group %s') % group_name, category='error')
273 273
274 274 return redirect(url('repos_groups'))
275 275
276 276 @HasReposGroupPermissionAnyDecorator('group.admin')
277 277 def delete_repos_group_user_perm(self, group_name):
278 278 """
279 279 DELETE an existing repository group permission user
280 280
281 281 :param group_name:
282 282 """
283 283 try:
284 284 if not c.rhodecode_user.is_admin:
285 285 if c.rhodecode_user.user_id == safe_int(request.POST['user_id']):
286 286 msg = _('Cannot revoke permission for yourself as admin')
287 287 h.flash(msg, category='warning')
288 288 raise Exception('revoke admin permission on self')
289 289 recursive = str2bool(request.POST.get('recursive', False))
290 290 ReposGroupModel().delete_permission(
291 291 repos_group=group_name, obj=request.POST['user_id'],
292 292 obj_type='user', recursive=recursive
293 293 )
294 294 Session().commit()
295 295 except Exception:
296 296 log.error(traceback.format_exc())
297 297 h.flash(_('An error occurred during deletion of group user'),
298 298 category='error')
299 299 raise HTTPInternalServerError()
300 300
301 301 @HasReposGroupPermissionAnyDecorator('group.admin')
302 302 def delete_repos_group_users_group_perm(self, group_name):
303 303 """
304 304 DELETE an existing repository group permission user group
305 305
306 306 :param group_name:
307 307 """
308 308
309 309 try:
310 310 recursive = str2bool(request.POST.get('recursive', False))
311 311 ReposGroupModel().delete_permission(
312 312 repos_group=group_name, obj=request.POST['users_group_id'],
313 313 obj_type='users_group', recursive=recursive
314 314 )
315 315 Session().commit()
316 316 except Exception:
317 317 log.error(traceback.format_exc())
318 318 h.flash(_('An error occurred during deletion of group'
319 319 ' user groups'),
320 320 category='error')
321 321 raise HTTPInternalServerError()
322 322
323 323 def show_by_name(self, group_name):
324 324 """
325 325 This is a proxy that does a lookup group_name -> id, and shows
326 326 the group by id view instead
327 327 """
328 328 group_name = group_name.rstrip('/')
329 329 id_ = RepoGroup.get_by_group_name(group_name)
330 330 if id_:
331 331 return self.show(id_.group_id)
332 332 raise HTTPNotFound
333 333
334 334 @HasReposGroupPermissionAnyDecorator('group.read', 'group.write',
335 335 'group.admin')
336 336 def show(self, group_name, format='html'):
337 337 """GET /repos_groups/group_name: Show a specific item"""
338 338 # url('repos_group', group_name=GROUP_NAME)
339 339
340 340 c.group = c.repos_group = ReposGroupModel()._get_repos_group(group_name)
341 341 c.group_repos = c.group.repositories.all()
342 342
343 343 #overwrite our cached list with current filter
344 344 gr_filter = c.group_repos
345 345 c.repo_cnt = 0
346 346
347 347 groups = RepoGroup.query().order_by(RepoGroup.group_name)\
348 348 .filter(RepoGroup.group_parent_id == c.group.group_id).all()
349 349 c.groups = self.scm_model.get_repos_groups(groups)
350 350
351 351 if not c.visual.lightweight_dashboard:
352 352 c.repos_list = self.scm_model.get_repos(all_repos=gr_filter)
353 353 ## lightweight version of dashboard
354 354 else:
355 355 c.repos_list = Repository.query()\
356 356 .filter(Repository.group_id == c.group.group_id)\
357 357 .order_by(func.lower(Repository.repo_name))\
358 358 .all()
359 359
360 360 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
361 361 admin=False)
362 362 #json used to render the grid
363 363 c.data = json.dumps(repos_data)
364 364
365 365 return render('admin/repos_groups/repos_groups.html')
366 366
367 367 @HasReposGroupPermissionAnyDecorator('group.admin')
368 368 def edit(self, group_name, format='html'):
369 369 """GET /repos_groups/group_name/edit: Form to edit an existing item"""
370 370 # url('edit_repos_group', group_name=GROUP_NAME)
371 371
372 372 c.repos_group = ReposGroupModel()._get_repos_group(group_name)
373 373 #we can only allow moving empty group if it's already a top-level
374 374 #group, ie has no parents, or we're admin
375 375 if HasPermissionAll('hg.admin')('group edit'):
376 376 #we're global admin, we're ok and we can create TOP level groups
377 377 allow_empty_group = True
378 378 elif not c.repos_group.parent_group:
379 379 allow_empty_group = True
380 380 else:
381 381 allow_empty_group = False
382 382
383 383 self.__load_defaults(allow_empty_group=allow_empty_group,
384 384 exclude_group_ids=[c.repos_group.group_id])
385 385 defaults = self.__load_data(c.repos_group.group_id)
386 386
387 387 return htmlfill.render(
388 388 render('admin/repos_groups/repos_groups_edit.html'),
389 389 defaults=defaults,
390 390 encoding="UTF-8",
391 391 force_defaults=False
392 392 )
@@ -1,87 +1,87
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.home
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Home controller for Rhodecode
7 7
8 8 :created_on: Feb 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27
28 28 from pylons import tmpl_context as c, request
29 29 from pylons.i18n.translation import _
30 30 from webob.exc import HTTPBadRequest
31 31 from sqlalchemy.sql.expression import func
32 32
33 33 import rhodecode
34 34 from rhodecode.lib import helpers as h
35 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.compat import json
36 36 from rhodecode.lib.auth import LoginRequired
37 37 from rhodecode.lib.base import BaseController, render
38 38 from rhodecode.model.db import Repository
39 39 from rhodecode.model.repo import RepoModel
40 40
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44
45 45 class HomeController(BaseController):
46 46
47 47 @LoginRequired()
48 48 def __before__(self):
49 49 super(HomeController, self).__before__()
50 50
51 51 def index(self):
52 52 c.groups = self.scm_model.get_repos_groups()
53 53 c.group = None
54 54
55 55 if not c.visual.lightweight_dashboard:
56 56 c.repos_list = self.scm_model.get_repos()
57 57 ## lightweight version of dashboard
58 58 else:
59 59 c.repos_list = Repository.query()\
60 60 .filter(Repository.group_id == None)\
61 61 .order_by(func.lower(Repository.repo_name))\
62 62 .all()
63 63
64 64 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
65 65 admin=False)
66 66 #json used to render the grid
67 67 c.data = json.dumps(repos_data)
68 68
69 69 return render('/index.html')
70 70
71 71 def repo_switcher(self):
72 72 if request.is_xhr:
73 73 all_repos = Repository.query().order_by(Repository.repo_name).all()
74 74 c.repos_list = self.scm_model.get_repos(all_repos,
75 75 sort_key='name_sort',
76 76 simple=True)
77 77 return render('/repo_switcher_list.html')
78 78 else:
79 79 raise HTTPBadRequest()
80 80
81 81 def branch_tag_switcher(self, repo_name):
82 82 if request.is_xhr:
83 83 c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
84 84 if c.rhodecode_db_repo:
85 85 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
86 86 return render('/switch_to_list.html')
87 87 raise HTTPBadRequest()
@@ -1,779 +1,779
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.utils
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Utilities library for RhodeCode
7 7
8 8 :created_on: Apr 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import re
28 28 import logging
29 29 import datetime
30 30 import traceback
31 31 import paste
32 32 import beaker
33 33 import tarfile
34 34 import shutil
35 35 import decorator
36 36 import warnings
37 37 from os.path import abspath
38 38 from os.path import dirname as dn, join as jn
39 39
40 40 from paste.script.command import Command, BadCommand
41 41
42 42 from mercurial import ui, config
43 43
44 44 from webhelpers.text import collapse, remove_formatting, strip_tags
45 45
46 46 from rhodecode.lib.vcs import get_backend
47 47 from rhodecode.lib.vcs.backends.base import BaseChangeset
48 48 from rhodecode.lib.vcs.utils.lazy import LazyProperty
49 49 from rhodecode.lib.vcs.utils.helpers import get_scm
50 50 from rhodecode.lib.vcs.exceptions import VCSError
51 51
52 52 from rhodecode.lib.caching_query import FromCache
53 53
54 54 from rhodecode.model import meta
55 55 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
56 56 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
57 57 from rhodecode.model.meta import Session
58 58 from rhodecode.model.repos_group import ReposGroupModel
59 59 from rhodecode.lib.utils2 import safe_str, safe_unicode
60 60 from rhodecode.lib.vcs.utils.fakemod import create_module
61 61
62 62 log = logging.getLogger(__name__)
63 63
64 64 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
65 65
66 66
67 67 def recursive_replace(str_, replace=' '):
68 68 """
69 69 Recursive replace of given sign to just one instance
70 70
71 71 :param str_: given string
72 72 :param replace: char to find and replace multiple instances
73 73
74 74 Examples::
75 75 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
76 76 'Mighty-Mighty-Bo-sstones'
77 77 """
78 78
79 79 if str_.find(replace * 2) == -1:
80 80 return str_
81 81 else:
82 82 str_ = str_.replace(replace * 2, replace)
83 83 return recursive_replace(str_, replace)
84 84
85 85
86 86 def repo_name_slug(value):
87 87 """
88 88 Return slug of name of repository
89 89 This function is called on each creation/modification
90 90 of repository to prevent bad names in repo
91 91 """
92 92
93 93 slug = remove_formatting(value)
94 94 slug = strip_tags(slug)
95 95
96 96 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
97 97 slug = slug.replace(c, '-')
98 98 slug = recursive_replace(slug, '-')
99 99 slug = collapse(slug, '-')
100 100 return slug
101 101
102 102
103 103 def get_repo_slug(request):
104 104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
105 105 if _repo:
106 106 _repo = _repo.rstrip('/')
107 107 return _repo
108 108
109 109
110 110 def get_repos_group_slug(request):
111 111 _group = request.environ['pylons.routes_dict'].get('group_name')
112 112 if _group:
113 113 _group = _group.rstrip('/')
114 114 return _group
115 115
116 116
117 117 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
118 118 """
119 119 Action logger for various actions made by users
120 120
121 121 :param user: user that made this action, can be a unique username string or
122 122 object containing user_id attribute
123 123 :param action: action to log, should be on of predefined unique actions for
124 124 easy translations
125 125 :param repo: string name of repository or object containing repo_id,
126 126 that action was made on
127 127 :param ipaddr: optional ip address from what the action was made
128 128 :param sa: optional sqlalchemy session
129 129
130 130 """
131 131
132 132 if not sa:
133 133 sa = meta.Session()
134 134
135 135 try:
136 136 if hasattr(user, 'user_id'):
137 137 user_obj = User.get(user.user_id)
138 138 elif isinstance(user, basestring):
139 139 user_obj = User.get_by_username(user)
140 140 else:
141 141 raise Exception('You have to provide a user object or a username')
142 142
143 143 if hasattr(repo, 'repo_id'):
144 144 repo_obj = Repository.get(repo.repo_id)
145 145 repo_name = repo_obj.repo_name
146 146 elif isinstance(repo, basestring):
147 147 repo_name = repo.lstrip('/')
148 148 repo_obj = Repository.get_by_repo_name(repo_name)
149 149 else:
150 150 repo_obj = None
151 151 repo_name = ''
152 152
153 153 user_log = UserLog()
154 154 user_log.user_id = user_obj.user_id
155 155 user_log.username = user_obj.username
156 156 user_log.action = safe_unicode(action)
157 157
158 158 user_log.repository = repo_obj
159 159 user_log.repository_name = repo_name
160 160
161 161 user_log.action_date = datetime.datetime.now()
162 162 user_log.user_ip = ipaddr
163 163 sa.add(user_log)
164 164
165 165 log.info('Logging action:%s on %s by user:%s ip:%s' %
166 166 (action, safe_unicode(repo), user_obj, ipaddr))
167 167 if commit:
168 168 sa.commit()
169 169 except Exception:
170 170 log.error(traceback.format_exc())
171 171 raise
172 172
173 173
174 174 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
175 175 """
176 176 Scans given path for repos and return (name,(type,path)) tuple
177 177
178 178 :param path: path to scan for repositories
179 179 :param recursive: recursive search and return names with subdirs in front
180 180 """
181 181
182 182 # remove ending slash for better results
183 183 path = path.rstrip(os.sep)
184 184 log.debug('now scanning in %s location recursive:%s...' % (path, recursive))
185 185
186 186 def _get_repos(p):
187 187 if not os.access(p, os.W_OK):
188 188 log.warn('ignoring repo path without write access: %s', p)
189 189 return
190 190 for dirpath in os.listdir(p):
191 191 if os.path.isfile(os.path.join(p, dirpath)):
192 192 continue
193 193 cur_path = os.path.join(p, dirpath)
194 194
195 195 # skip removed repos
196 196 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
197 197 continue
198 198
199 199 #skip .<somethin> dirs
200 200 if dirpath.startswith('.'):
201 201 continue
202 202
203 203 try:
204 204 scm_info = get_scm(cur_path)
205 205 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
206 206 except VCSError:
207 207 if not recursive:
208 208 continue
209 209 #check if this dir containts other repos for recursive scan
210 210 rec_path = os.path.join(p, dirpath)
211 211 if os.path.isdir(rec_path):
212 212 for inner_scm in _get_repos(rec_path):
213 213 yield inner_scm
214 214
215 215 return _get_repos(path)
216 216
217 217
218 218 def is_valid_repo(repo_name, base_path, scm=None):
219 219 """
220 220 Returns True if given path is a valid repository False otherwise.
221 221 If scm param is given also compare if given scm is the same as expected
222 222 from scm parameter
223 223
224 224 :param repo_name:
225 225 :param base_path:
226 226 :param scm:
227 227
228 228 :return True: if given path is a valid repository
229 229 """
230 230 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
231 231
232 232 try:
233 233 scm_ = get_scm(full_path)
234 234 if scm:
235 235 return scm_[0] == scm
236 236 return True
237 237 except VCSError:
238 238 return False
239 239
240 240
241 241 def is_valid_repos_group(repos_group_name, base_path, skip_path_check=False):
242 242 """
243 243 Returns True if given path is a repository group False otherwise
244 244
245 245 :param repo_name:
246 246 :param base_path:
247 247 """
248 248 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
249 249
250 250 # check if it's not a repo
251 251 if is_valid_repo(repos_group_name, base_path):
252 252 return False
253 253
254 254 try:
255 255 # we need to check bare git repos at higher level
256 256 # since we might match branches/hooks/info/objects or possible
257 257 # other things inside bare git repo
258 258 get_scm(os.path.dirname(full_path))
259 259 return False
260 260 except VCSError:
261 261 pass
262 262
263 263 # check if it's a valid path
264 264 if skip_path_check or os.path.isdir(full_path):
265 265 return True
266 266
267 267 return False
268 268
269 269
270 270 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
271 271 while True:
272 272 ok = raw_input(prompt)
273 273 if ok in ('y', 'ye', 'yes'):
274 274 return True
275 275 if ok in ('n', 'no', 'nop', 'nope'):
276 276 return False
277 277 retries = retries - 1
278 278 if retries < 0:
279 279 raise IOError
280 280 print complaint
281 281
282 282 #propagated from mercurial documentation
283 283 ui_sections = ['alias', 'auth',
284 284 'decode/encode', 'defaults',
285 285 'diff', 'email',
286 286 'extensions', 'format',
287 287 'merge-patterns', 'merge-tools',
288 288 'hooks', 'http_proxy',
289 289 'smtp', 'patch',
290 290 'paths', 'profiling',
291 291 'server', 'trusted',
292 292 'ui', 'web', ]
293 293
294 294
295 295 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
296 296 """
297 297 A function that will read python rc files or database
298 298 and make an mercurial ui object from read options
299 299
300 300 :param path: path to mercurial config file
301 301 :param checkpaths: check the path
302 302 :param read_from: read from 'file' or 'db'
303 303 """
304 304
305 305 baseui = ui.ui()
306 306
307 307 # clean the baseui object
308 308 baseui._ocfg = config.config()
309 309 baseui._ucfg = config.config()
310 310 baseui._tcfg = config.config()
311 311
312 312 if read_from == 'file':
313 313 if not os.path.isfile(path):
314 314 log.debug('hgrc file is not present at %s, skipping...' % path)
315 315 return False
316 316 log.debug('reading hgrc from %s' % path)
317 317 cfg = config.config()
318 318 cfg.read(path)
319 319 for section in ui_sections:
320 320 for k, v in cfg.items(section):
321 321 log.debug('settings ui from file: [%s] %s=%s' % (section, k, v))
322 322 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
323 323
324 324 elif read_from == 'db':
325 325 sa = meta.Session()
326 326 ret = sa.query(RhodeCodeUi)\
327 327 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
328 328 .all()
329 329
330 330 hg_ui = ret
331 331 for ui_ in hg_ui:
332 332 if ui_.ui_active:
333 333 log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
334 334 ui_.ui_key, ui_.ui_value)
335 335 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
336 336 safe_str(ui_.ui_value))
337 337 if ui_.ui_key == 'push_ssl':
338 338 # force set push_ssl requirement to False, rhodecode
339 339 # handles that
340 340 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
341 341 False)
342 342 if clear_session:
343 343 meta.Session.remove()
344 344 return baseui
345 345
346 346
347 347 def set_rhodecode_config(config):
348 348 """
349 349 Updates pylons config with new settings from database
350 350
351 351 :param config:
352 352 """
353 353 hgsettings = RhodeCodeSetting.get_app_settings()
354 354
355 355 for k, v in hgsettings.items():
356 356 config[k] = v
357 357
358 358
359 359 def map_groups(path):
360 360 """
361 361 Given a full path to a repository, create all nested groups that this
362 362 repo is inside. This function creates parent-child relationships between
363 363 groups and creates default perms for all new groups.
364 364
365 365 :param paths: full path to repository
366 366 """
367 367 sa = meta.Session()
368 368 groups = path.split(Repository.url_sep())
369 369 parent = None
370 370 group = None
371 371
372 372 # last element is repo in nested groups structure
373 373 groups = groups[:-1]
374 374 rgm = ReposGroupModel(sa)
375 375 for lvl, group_name in enumerate(groups):
376 376 group_name = '/'.join(groups[:lvl] + [group_name])
377 377 group = RepoGroup.get_by_group_name(group_name)
378 378 desc = '%s group' % group_name
379 379
380 380 # skip folders that are now removed repos
381 381 if REMOVED_REPO_PAT.match(group_name):
382 382 break
383 383
384 384 if group is None:
385 385 log.debug('creating group level: %s group_name: %s' % (lvl,
386 386 group_name))
387 387 group = RepoGroup(group_name, parent)
388 388 group.group_description = desc
389 389 sa.add(group)
390 390 rgm._create_default_perms(group)
391 391 sa.flush()
392 392 parent = group
393 393 return group
394 394
395 395
396 396 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
397 397 install_git_hook=False):
398 398 """
399 399 maps all repos given in initial_repo_list, non existing repositories
400 400 are created, if remove_obsolete is True it also check for db entries
401 401 that are not in initial_repo_list and removes them.
402 402
403 403 :param initial_repo_list: list of repositories found by scanning methods
404 404 :param remove_obsolete: check for obsolete entries in database
405 405 :param install_git_hook: if this is True, also check and install githook
406 406 for a repo if missing
407 407 """
408 408 from rhodecode.model.repo import RepoModel
409 409 from rhodecode.model.scm import ScmModel
410 410 sa = meta.Session()
411 411 rm = RepoModel()
412 412 user = sa.query(User).filter(User.admin == True).first()
413 413 if user is None:
414 414 raise Exception('Missing administrative account!')
415 415 added = []
416 416
417 417 ##creation defaults
418 418 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
419 419 enable_statistics = defs.get('repo_enable_statistics')
420 420 enable_locking = defs.get('repo_enable_locking')
421 421 enable_downloads = defs.get('repo_enable_downloads')
422 422 private = defs.get('repo_private')
423 423
424 424 for name, repo in initial_repo_list.items():
425 425 group = map_groups(name)
426 426 db_repo = rm.get_by_repo_name(name)
427 427 # found repo that is on filesystem not in RhodeCode database
428 428 if not db_repo:
429 429 log.info('repository %s not found, creating now' % name)
430 430 added.append(name)
431 431 desc = (repo.description
432 432 if repo.description != 'unknown'
433 433 else '%s repository' % name)
434 434
435 435 new_repo = rm.create_repo(
436 436 repo_name=name,
437 437 repo_type=repo.alias,
438 438 description=desc,
439 439 repos_group=getattr(group, 'group_id', None),
440 440 owner=user,
441 441 just_db=True,
442 442 enable_locking=enable_locking,
443 443 enable_downloads=enable_downloads,
444 444 enable_statistics=enable_statistics,
445 445 private=private
446 446 )
447 447 # we added that repo just now, and make sure it has githook
448 448 # installed
449 449 if new_repo.repo_type == 'git':
450 450 ScmModel().install_git_hook(new_repo.scm_instance)
451 451 new_repo.update_changeset_cache()
452 452 elif install_git_hook:
453 453 if db_repo.repo_type == 'git':
454 454 ScmModel().install_git_hook(db_repo.scm_instance)
455 455 # during starting install all cache keys for all repositories in the
456 456 # system, this will register all repos and multiple instances
457 457 cache_key = CacheInvalidation._get_cache_key(name)
458 458 log.debug("Creating invalidation cache key for %s: %s", name, cache_key)
459 459 CacheInvalidation.invalidate(name)
460 460
461 461 sa.commit()
462 462 removed = []
463 463 if remove_obsolete:
464 464 # remove from database those repositories that are not in the filesystem
465 465 for repo in sa.query(Repository).all():
466 466 if repo.repo_name not in initial_repo_list.keys():
467 467 log.debug("Removing non-existing repository found in db `%s`" %
468 468 repo.repo_name)
469 469 try:
470 470 removed.append(repo.repo_name)
471 471 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
472 472 sa.commit()
473 473 except Exception:
474 474 #don't hold further removals on error
475 475 log.error(traceback.format_exc())
476 476 sa.rollback()
477 477 return added, removed
478 478
479 479
480 480 # set cache regions for beaker so celery can utilise it
481 481 def add_cache(settings):
482 482 cache_settings = {'regions': None}
483 483 for key in settings.keys():
484 484 for prefix in ['beaker.cache.', 'cache.']:
485 485 if key.startswith(prefix):
486 486 name = key.split(prefix)[1].strip()
487 487 cache_settings[name] = settings[key].strip()
488 488 if cache_settings['regions']:
489 489 for region in cache_settings['regions'].split(','):
490 490 region = region.strip()
491 491 region_settings = {}
492 492 for key, value in cache_settings.items():
493 493 if key.startswith(region):
494 494 region_settings[key.split('.')[1]] = value
495 495 region_settings['expire'] = int(region_settings.get('expire',
496 496 60))
497 497 region_settings.setdefault('lock_dir',
498 498 cache_settings.get('lock_dir'))
499 499 region_settings.setdefault('data_dir',
500 500 cache_settings.get('data_dir'))
501 501
502 502 if 'type' not in region_settings:
503 503 region_settings['type'] = cache_settings.get('type',
504 504 'memory')
505 505 beaker.cache.cache_regions[region] = region_settings
506 506
507 507
508 508 def load_rcextensions(root_path):
509 509 import rhodecode
510 510 from rhodecode.config import conf
511 511
512 512 path = os.path.join(root_path, 'rcextensions', '__init__.py')
513 513 if os.path.isfile(path):
514 514 rcext = create_module('rc', path)
515 515 EXT = rhodecode.EXTENSIONS = rcext
516 516 log.debug('Found rcextensions now loading %s...' % rcext)
517 517
518 518 # Additional mappings that are not present in the pygments lexers
519 519 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
520 520
521 521 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
522 522
523 523 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
524 524 log.debug('settings custom INDEX_EXTENSIONS')
525 525 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
526 526
527 527 #ADDITIONAL MAPPINGS
528 528 log.debug('adding extra into INDEX_EXTENSIONS')
529 529 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
530 530
531 531 # auto check if the module is not missing any data, set to default if is
532 532 # this will help autoupdate new feature of rcext module
533 533 from rhodecode.config import rcextensions
534 534 for k in dir(rcextensions):
535 535 if not k.startswith('_') and not hasattr(EXT, k):
536 536 setattr(EXT, k, getattr(rcextensions, k))
537 537
538 538
539 539 def get_custom_lexer(extension):
540 540 """
541 541 returns a custom lexer if it's defined in rcextensions module, or None
542 542 if there's no custom lexer defined
543 543 """
544 544 import rhodecode
545 545 from pygments import lexers
546 546 #check if we didn't define this extension as other lexer
547 547 if rhodecode.EXTENSIONS and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
548 548 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
549 549 return lexers.get_lexer_by_name(_lexer_name)
550 550
551 551
552 552 #==============================================================================
553 553 # TEST FUNCTIONS AND CREATORS
554 554 #==============================================================================
555 555 def create_test_index(repo_location, config, full_index):
556 556 """
557 557 Makes default test index
558 558
559 559 :param config: test config
560 560 :param full_index:
561 561 """
562 562
563 563 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
564 564 from rhodecode.lib.pidlock import DaemonLock, LockHeld
565 565
566 566 repo_location = repo_location
567 567
568 568 index_location = os.path.join(config['app_conf']['index_dir'])
569 569 if not os.path.exists(index_location):
570 570 os.makedirs(index_location)
571 571
572 572 try:
573 573 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
574 574 WhooshIndexingDaemon(index_location=index_location,
575 575 repo_location=repo_location)\
576 576 .run(full_index=full_index)
577 577 l.release()
578 578 except LockHeld:
579 579 pass
580 580
581 581
582 582 def create_test_env(repos_test_path, config):
583 583 """
584 584 Makes a fresh database and
585 585 install test repository into tmp dir
586 586 """
587 587 from rhodecode.lib.db_manage import DbManage
588 588 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
589 589
590 590 # PART ONE create db
591 591 dbconf = config['sqlalchemy.db1.url']
592 592 log.debug('making test db %s' % dbconf)
593 593
594 594 # create test dir if it doesn't exist
595 595 if not os.path.isdir(repos_test_path):
596 596 log.debug('Creating testdir %s' % repos_test_path)
597 597 os.makedirs(repos_test_path)
598 598
599 599 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
600 600 tests=True)
601 601 dbmanage.create_tables(override=True)
602 602 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
603 603 dbmanage.create_default_user()
604 604 dbmanage.admin_prompt()
605 605 dbmanage.create_permissions()
606 606 dbmanage.populate_default_permissions()
607 607 Session().commit()
608 608 # PART TWO make test repo
609 609 log.debug('making test vcs repositories')
610 610
611 611 idx_path = config['app_conf']['index_dir']
612 612 data_path = config['app_conf']['cache_dir']
613 613
614 614 #clean index and data
615 615 if idx_path and os.path.exists(idx_path):
616 616 log.debug('remove %s' % idx_path)
617 617 shutil.rmtree(idx_path)
618 618
619 619 if data_path and os.path.exists(data_path):
620 620 log.debug('remove %s' % data_path)
621 621 shutil.rmtree(data_path)
622 622
623 623 #CREATE DEFAULT TEST REPOS
624 624 cur_dir = dn(dn(abspath(__file__)))
625 625 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
626 626 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
627 627 tar.close()
628 628
629 629 cur_dir = dn(dn(abspath(__file__)))
630 630 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
631 631 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
632 632 tar.close()
633 633
634 634 #LOAD VCS test stuff
635 635 from rhodecode.tests.vcs import setup_package
636 636 setup_package()
637 637
638 638
639 639 #==============================================================================
640 640 # PASTER COMMANDS
641 641 #==============================================================================
642 642 class BasePasterCommand(Command):
643 643 """
644 644 Abstract Base Class for paster commands.
645 645
646 646 The celery commands are somewhat aggressive about loading
647 647 celery.conf, and since our module sets the `CELERY_LOADER`
648 648 environment variable to our loader, we have to bootstrap a bit and
649 649 make sure we've had a chance to load the pylons config off of the
650 650 command line, otherwise everything fails.
651 651 """
652 652 min_args = 1
653 653 min_args_error = "Please provide a paster config file as an argument."
654 654 takes_config_file = 1
655 655 requires_config_file = True
656 656
657 657 def notify_msg(self, msg, log=False):
658 658 """Make a notification to user, additionally if logger is passed
659 659 it logs this action using given logger
660 660
661 661 :param msg: message that will be printed to user
662 662 :param log: logging instance, to use to additionally log this message
663 663
664 664 """
665 665 if log and isinstance(log, logging):
666 666 log(msg)
667 667
668 668 def run(self, args):
669 669 """
670 670 Overrides Command.run
671 671
672 672 Checks for a config file argument and loads it.
673 673 """
674 674 if len(args) < self.min_args:
675 675 raise BadCommand(
676 676 self.min_args_error % {'min_args': self.min_args,
677 677 'actual_args': len(args)})
678 678
679 679 # Decrement because we're going to lob off the first argument.
680 680 # @@ This is hacky
681 681 self.min_args -= 1
682 682 self.bootstrap_config(args[0])
683 683 self.update_parser()
684 684 return super(BasePasterCommand, self).run(args[1:])
685 685
686 686 def update_parser(self):
687 687 """
688 688 Abstract method. Allows for the class's parser to be updated
689 689 before the superclass's `run` method is called. Necessary to
690 690 allow options/arguments to be passed through to the underlying
691 691 celery command.
692 692 """
693 693 raise NotImplementedError("Abstract Method.")
694 694
695 695 def bootstrap_config(self, conf):
696 696 """
697 697 Loads the pylons configuration.
698 698 """
699 699 from pylons import config as pylonsconfig
700 700
701 701 self.path_to_ini_file = os.path.realpath(conf)
702 702 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
703 703 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
704 704
705 705 def _init_session(self):
706 706 """
707 707 Inits SqlAlchemy Session
708 708 """
709 709 logging.config.fileConfig(self.path_to_ini_file)
710 710 from pylons import config
711 711 from rhodecode.model import init_model
712 712 from rhodecode.lib.utils2 import engine_from_config
713 713
714 714 #get to remove repos !!
715 715 add_cache(config)
716 716 engine = engine_from_config(config, 'sqlalchemy.db1.')
717 717 init_model(engine)
718 718
719 719
720 720 def check_git_version():
721 721 """
722 722 Checks what version of git is installed in system, and issues a warning
723 723 if it's too old for RhodeCode to properly work.
724 724 """
725 725 from rhodecode import BACKENDS
726 726 from rhodecode.lib.vcs.backends.git.repository import GitRepository
727 727 from distutils.version import StrictVersion
728 728
729 729 stdout, stderr = GitRepository._run_git_command('--version', _bare=True,
730 730 _safe=True)
731 731
732 732 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
733 733 if len(ver.split('.')) > 3:
734 734 #StrictVersion needs to be only 3 element type
735 735 ver = '.'.join(ver.split('.')[:3])
736 736 try:
737 737 _ver = StrictVersion(ver)
738 738 except Exception:
739 739 _ver = StrictVersion('0.0.0')
740 740 stderr = traceback.format_exc()
741 741
742 742 req_ver = '1.7.4'
743 743 to_old_git = False
744 744 if _ver < StrictVersion(req_ver):
745 745 to_old_git = True
746 746
747 747 if 'git' in BACKENDS:
748 748 log.debug('GIT version detected: %s' % stdout)
749 749 if stderr:
750 750 log.warning('Unable to detect git version, org error was: %r' % stderr)
751 751 elif to_old_git:
752 752 log.warning('RhodeCode detected git version %s, which is too old '
753 753 'for the system to function properly. Make sure '
754 754 'its version is at least %s' % (ver, req_ver))
755 755 return _ver
756 756
757 757
758 758 @decorator.decorator
759 759 def jsonify(func, *args, **kwargs):
760 760 """Action decorator that formats output for JSON
761 761
762 762 Given a function that will return content, this decorator will turn
763 763 the result into JSON, with a content-type of 'application/json' and
764 764 output it.
765 765
766 766 """
767 767 from pylons.decorators.util import get_pylons
768 from rhodecode.lib.ext_json import json
768 from rhodecode.lib.compat import json
769 769 pylons = get_pylons(args)
770 770 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
771 771 data = func(*args, **kwargs)
772 772 if isinstance(data, (list, tuple)):
773 773 msg = "JSON responses with Array envelopes are susceptible to " \
774 774 "cross-site data leak attacks, see " \
775 775 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
776 776 warnings.warn(msg, Warning, 2)
777 777 log.warning(msg)
778 778 log.debug("Returning JSON wrapped action output")
779 779 return json.dumps(data, encoding='utf-8')
General Comments 0
You need to be logged in to leave comments. Login now