##// END OF EJS Templates
hooks: new hook support for python3
super-admin -
r5081:fb3d7403 default
parent child Browse files
Show More
@@ -94,11 +94,9 b' def _get_scm_size(alias, root_path):'
94 def repo_size(extras):
94 def repo_size(extras):
95 """Present size of repository after push."""
95 """Present size of repository after push."""
96 repo = Repository.get_by_repo_name(extras.repository)
96 repo = Repository.get_by_repo_name(extras.repository)
97 vcs_part = safe_str('.%s' % repo.repo_type)
97 vcs_part = f'.{repo.repo_type}'
98 size_vcs, size_root, size_total = _get_scm_size(vcs_part,
98 size_vcs, size_root, size_total = _get_scm_size(vcs_part, repo.repo_full_path)
99 repo.repo_full_path)
99 msg = (f'RhodeCode: `{repo.repo_name}` size summary {vcs_part}:{size_vcs} repo:{size_root} total:{size_total}\n')
100 msg = ('Repository `%s` size summary %s:%s repo:%s total:%s\n'
101 % (repo.repo_name, vcs_part, size_vcs, size_root, size_total))
102 return HookResponse(0, msg)
100 return HookResponse(0, msg)
103
101
104
102
@@ -126,8 +124,8 b' def pre_push(extras):'
126
124
127 hook_response = ''
125 hook_response = ''
128 if not is_shadow_repo(extras):
126 if not is_shadow_repo(extras):
127
129 if extras.commit_ids and extras.check_branch_perms:
128 if extras.commit_ids and extras.check_branch_perms:
130
131 auth_user = user.AuthUser()
129 auth_user = user.AuthUser()
132 repo = Repository.get_by_repo_name(extras.repository)
130 repo = Repository.get_by_repo_name(extras.repository)
133 affected_branches = []
131 affected_branches = []
@@ -155,11 +153,10 b' def pre_push(extras):'
155 elif branch_perm == 'branch.push' and is_forced is False:
153 elif branch_perm == 'branch.push' and is_forced is False:
156 continue
154 continue
157 elif branch_perm == 'branch.push' and is_forced is True:
155 elif branch_perm == 'branch.push' and is_forced is True:
158 halt_message = 'Branch `{}` changes rejected by rule {}. ' \
156 halt_message = f'Branch `{branch_name}` changes rejected by rule {rule}. ' \
159 'FORCE PUSH FORBIDDEN.'.format(branch_name, rule)
157 f'FORCE PUSH FORBIDDEN.'
160 else:
158 else:
161 halt_message = 'Branch `{}` changes rejected by rule {}.'.format(
159 halt_message = f'Branch `{branch_name}` changes rejected by rule {rule}.'
162 branch_name, rule)
163
160
164 if halt_message:
161 if halt_message:
165 _http_ret = HTTPBranchProtected(halt_message)
162 _http_ret = HTTPBranchProtected(halt_message)
@@ -281,7 +278,7 b' def post_push(extras):'
281 # make lock is a tri state False, True, None. We only release lock on False
278 # make lock is a tri state False, True, None. We only release lock on False
282 if extras.make_lock is False and not is_shadow_repo(extras):
279 if extras.make_lock is False and not is_shadow_repo(extras):
283 Repository.unlock(Repository.get_by_repo_name(extras.repository))
280 Repository.unlock(Repository.get_by_repo_name(extras.repository))
284 msg = 'Released lock on repo `{}`\n'.format(safe_str(extras.repository))
281 msg = f'Released lock on repo `{extras.repository}`\n'
285 output += msg
282 output += msg
286
283
287 if extras.locked_by[0]:
284 if extras.locked_by[0]:
@@ -299,12 +296,12 b' def post_push(extras):'
299 safe_str(extras.server_url), safe_str(extras.repository))
296 safe_str(extras.server_url), safe_str(extras.repository))
300
297
301 for branch_name in extras.new_refs['branches']:
298 for branch_name in extras.new_refs['branches']:
302 output += 'RhodeCode: open pull request link: {}\n'.format(
299 pr_link = tmpl.format(ref_type='branch', ref_name=safe_str(branch_name))
303 tmpl.format(ref_type='branch', ref_name=safe_str(branch_name)))
300 output += f'RhodeCode: open pull request link: {pr_link}\n'
304
301
305 for book_name in extras.new_refs['bookmarks']:
302 for book_name in extras.new_refs['bookmarks']:
306 output += 'RhodeCode: open pull request link: {}\n'.format(
303 pr_link = tmpl.format(ref_type='bookmark', ref_name=safe_str(book_name))
307 tmpl.format(ref_type='bookmark', ref_name=safe_str(book_name)))
304 output += f'RhodeCode: open pull request link: {pr_link}\n'
308
305
309 hook_response = ''
306 hook_response = ''
310 if not is_shadow_repo(extras):
307 if not is_shadow_repo(extras):
@@ -319,9 +316,7 b' def post_push(extras):'
319
316
320
317
321 def _locked_by_explanation(repo_name, user_name, reason):
318 def _locked_by_explanation(repo_name, user_name, reason):
322 message = (
319 message = f'Repository `{repo_name}` locked by user `{user_name}`. Reason:`{reason}`'
323 'Repository `%s` locked by user `%s`. Reason:`%s`'
324 % (repo_name, user_name, reason))
325 return message
320 return message
326
321
327
322
@@ -25,6 +25,7 b' import traceback'
25 import threading
25 import threading
26 import socket
26 import socket
27 import msgpack
27 import msgpack
28 import gevent
28
29
29 from http.server import BaseHTTPRequestHandler
30 from http.server import BaseHTTPRequestHandler
30 from socketserver import TCPServer
31 from socketserver import TCPServer
@@ -43,6 +44,23 b' log = logging.getLogger(__name__)'
43
44
44 class HooksHttpHandler(BaseHTTPRequestHandler):
45 class HooksHttpHandler(BaseHTTPRequestHandler):
45
46
47 JSON_HOOKS_PROTO = 'json.v1'
48 MSGPACK_HOOKS_PROTO = 'msgpack.v1'
49 # starting with RhodeCode 5.0.0 MsgPack is the default, prior it used json
50 DEFAULT_HOOKS_PROTO = MSGPACK_HOOKS_PROTO
51
52 @classmethod
53 def serialize_data(cls, data, proto=DEFAULT_HOOKS_PROTO):
54 if proto == cls.MSGPACK_HOOKS_PROTO:
55 return msgpack.packb(data)
56 return json.dumps(data)
57
58 @classmethod
59 def deserialize_data(cls, data, proto=DEFAULT_HOOKS_PROTO):
60 if proto == cls.MSGPACK_HOOKS_PROTO:
61 return msgpack.unpackb(data)
62 return json.loads(data)
63
46 def do_POST(self):
64 def do_POST(self):
47 hooks_proto, method, extras = self._read_request()
65 hooks_proto, method, extras = self._read_request()
48 log.debug('Handling HooksHttpHandler %s with %s proto', method, hooks_proto)
66 log.debug('Handling HooksHttpHandler %s with %s proto', method, hooks_proto)
@@ -62,6 +80,7 b' class HooksHttpHandler(BaseHTTPRequestHa'
62 try:
80 try:
63 hooks = Hooks(request=request, log_prefix='HOOKS: {} '.format(self.server.server_address))
81 hooks = Hooks(request=request, log_prefix='HOOKS: {} '.format(self.server.server_address))
64 result = self._call_hook_method(hooks, method, extras)
82 result = self._call_hook_method(hooks, method, extras)
83
65 except Exception as e:
84 except Exception as e:
66 exc_tb = traceback.format_exc()
85 exc_tb = traceback.format_exc()
67 result = {
86 result = {
@@ -73,26 +92,30 b' class HooksHttpHandler(BaseHTTPRequestHa'
73
92
74 def _read_request(self):
93 def _read_request(self):
75 length = int(self.headers['Content-Length'])
94 length = int(self.headers['Content-Length'])
76 hooks_proto = self.headers.get('rc-hooks-protocol') or 'json.v1'
95 # respect sent headers, fallback to OLD proto for compatability
77 if hooks_proto == 'msgpack.v1':
96 hooks_proto = self.headers.get('rc-hooks-protocol') or self.JSON_HOOKS_PROTO
97 if hooks_proto == self.MSGPACK_HOOKS_PROTO:
78 # support for new vcsserver msgpack based protocol hooks
98 # support for new vcsserver msgpack based protocol hooks
79 data = msgpack.unpackb(self.rfile.read(length), raw=False)
99 body = self.rfile.read(length)
100 data = self.deserialize_data(body)
80 else:
101 else:
81 body = self.rfile.read(length)
102 body = self.rfile.read(length)
82 data = json.loads(body)
103 data = self.deserialize_data(body)
83
104
84 return hooks_proto, data['method'], data['extras']
105 return hooks_proto, data['method'], data['extras']
85
106
86 def _write_response(self, hooks_proto, result):
107 def _write_response(self, hooks_proto, result):
87 self.send_response(200)
108 self.send_response(200)
88 if hooks_proto == 'msgpack.v1':
109 if hooks_proto == self.MSGPACK_HOOKS_PROTO:
89 self.send_header("Content-type", "application/msgpack")
110 self.send_header("Content-type", "application/msgpack")
90 self.end_headers()
111 self.end_headers()
91 self.wfile.write(msgpack.packb(result))
112 data = self.serialize_data(result)
113 self.wfile.write(data)
92 else:
114 else:
93 self.send_header("Content-type", "text/json")
115 self.send_header("Content-type", "text/json")
94 self.end_headers()
116 self.end_headers()
95 self.wfile.write(json.dumps(result))
117 data = self.serialize_data(result)
118 self.wfile.write(data)
96
119
97 def _call_hook_method(self, hooks, method, extras):
120 def _call_hook_method(self, hooks, method, extras):
98 try:
121 try:
@@ -110,7 +133,7 b' class HooksHttpHandler(BaseHTTPRequestHa'
110 message = format % args
133 message = format % args
111
134
112 log.debug(
135 log.debug(
113 "HOOKS: %s - - [%s] %s", self.client_address,
136 "HOOKS: client=%s - - [%s] %s", self.client_address,
114 self.log_date_time_string(), message)
137 self.log_date_time_string(), message)
115
138
116
139
@@ -133,18 +156,25 b' class ThreadedHookCallbackDaemon(object)'
133 _callback_thread = None
156 _callback_thread = None
134 _daemon = None
157 _daemon = None
135 _done = False
158 _done = False
159 use_gevent = False
136
160
137 def __init__(self, txn_id=None, host=None, port=None):
161 def __init__(self, txn_id=None, host=None, port=None):
138 self._prepare(txn_id=txn_id, host=host, port=port)
162 self._prepare(txn_id=txn_id, host=host, port=port)
163 if self.use_gevent:
164 self._run_func = self._run_gevent
165 self._stop_func = self._stop_gevent
166 else:
167 self._run_func = self._run
168 self._stop_func = self._stop
139
169
140 def __enter__(self):
170 def __enter__(self):
141 log.debug('Running `%s` callback daemon', self.__class__.__name__)
171 log.debug('Running `%s` callback daemon', self.__class__.__name__)
142 self._run()
172 self._run_func()
143 return self
173 return self
144
174
145 def __exit__(self, exc_type, exc_val, exc_tb):
175 def __exit__(self, exc_type, exc_val, exc_tb):
146 log.debug('Exiting `%s` callback daemon', self.__class__.__name__)
176 log.debug('Exiting `%s` callback daemon', self.__class__.__name__)
147 self._stop()
177 self._stop_func()
148
178
149 def _prepare(self, txn_id=None, host=None, port=None):
179 def _prepare(self, txn_id=None, host=None, port=None):
150 raise NotImplementedError()
180 raise NotImplementedError()
@@ -155,6 +185,12 b' class ThreadedHookCallbackDaemon(object)'
155 def _stop(self):
185 def _stop(self):
156 raise NotImplementedError()
186 raise NotImplementedError()
157
187
188 def _run_gevent(self):
189 raise NotImplementedError()
190
191 def _stop_gevent(self):
192 raise NotImplementedError()
193
158
194
159 class HttpHooksCallbackDaemon(ThreadedHookCallbackDaemon):
195 class HttpHooksCallbackDaemon(ThreadedHookCallbackDaemon):
160 """
196 """
@@ -167,6 +203,8 b' class HttpHooksCallbackDaemon(ThreadedHo'
167 # request and wastes cpu at all other times.
203 # request and wastes cpu at all other times.
168 POLL_INTERVAL = 0.01
204 POLL_INTERVAL = 0.01
169
205
206 use_gevent = False
207
170 @property
208 @property
171 def _hook_prefix(self):
209 def _hook_prefix(self):
172 return 'HOOKS: {} '.format(self.hooks_uri)
210 return 'HOOKS: {} '.format(self.hooks_uri)
@@ -203,7 +241,7 b' class HttpHooksCallbackDaemon(ThreadedHo'
203 self._daemon.request = get_current_request()
241 self._daemon.request = get_current_request()
204
242
205 def _run(self):
243 def _run(self):
206 log.debug("Running event loop of callback daemon in background thread")
244 log.debug("Running thread-based loop of callback daemon in background")
207 callback_thread = threading.Thread(
245 callback_thread = threading.Thread(
208 target=self._daemon.serve_forever,
246 target=self._daemon.serve_forever,
209 kwargs={'poll_interval': self.POLL_INTERVAL})
247 kwargs={'poll_interval': self.POLL_INTERVAL})
@@ -211,6 +249,19 b' class HttpHooksCallbackDaemon(ThreadedHo'
211 callback_thread.start()
249 callback_thread.start()
212 self._callback_thread = callback_thread
250 self._callback_thread = callback_thread
213
251
252 def _run_gevent(self):
253 log.debug("Running gevent-based loop of callback daemon in background")
254 # create a new greenlet for the daemon's serve_forever method
255 callback_greenlet = gevent.spawn(
256 self._daemon.serve_forever,
257 poll_interval=self.POLL_INTERVAL)
258
259 # store reference to greenlet
260 self._callback_greenlet = callback_greenlet
261
262 # switch to this greenlet
263 gevent.sleep(0.01)
264
214 def _stop(self):
265 def _stop(self):
215 log.debug("Waiting for background thread to finish.")
266 log.debug("Waiting for background thread to finish.")
216 self._daemon.shutdown()
267 self._daemon.shutdown()
@@ -225,6 +276,29 b' class HttpHooksCallbackDaemon(ThreadedHo'
225
276
226 log.debug("Background thread done.")
277 log.debug("Background thread done.")
227
278
279 def _stop_gevent(self):
280 log.debug("Waiting for background greenlet to finish.")
281
282 # if greenlet exists and is running
283 if self._callback_greenlet and not self._callback_greenlet.dead:
284 # shutdown daemon if it exists
285 if self._daemon:
286 self._daemon.shutdown()
287
288 # kill the greenlet
289 self._callback_greenlet.kill()
290
291 self._daemon = None
292 self._callback_greenlet = None
293
294 if self.txn_id:
295 txn_id_file = get_txn_id_data_path(self.txn_id)
296 log.debug('Cleaning up TXN ID %s', txn_id_file)
297 if os.path.isfile(txn_id_file):
298 os.remove(txn_id_file)
299
300 log.debug("Background greenlet done.")
301
228
302
229 def get_txn_id_data_path(txn_id):
303 def get_txn_id_data_path(txn_id):
230 import rhodecode
304 import rhodecode
General Comments 0
You need to be logged in to leave comments. Login now