##// 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 94 def repo_size(extras):
95 95 """Present size of repository after push."""
96 96 repo = Repository.get_by_repo_name(extras.repository)
97 vcs_part = safe_str('.%s' % repo.repo_type)
98 size_vcs, size_root, size_total = _get_scm_size(vcs_part,
99 repo.repo_full_path)
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))
97 vcs_part = f'.{repo.repo_type}'
98 size_vcs, size_root, size_total = _get_scm_size(vcs_part, repo.repo_full_path)
99 msg = (f'RhodeCode: `{repo.repo_name}` size summary {vcs_part}:{size_vcs} repo:{size_root} total:{size_total}\n')
102 100 return HookResponse(0, msg)
103 101
104 102
@@ -126,8 +124,8 b' def pre_push(extras):'
126 124
127 125 hook_response = ''
128 126 if not is_shadow_repo(extras):
127
129 128 if extras.commit_ids and extras.check_branch_perms:
130
131 129 auth_user = user.AuthUser()
132 130 repo = Repository.get_by_repo_name(extras.repository)
133 131 affected_branches = []
@@ -155,11 +153,10 b' def pre_push(extras):'
155 153 elif branch_perm == 'branch.push' and is_forced is False:
156 154 continue
157 155 elif branch_perm == 'branch.push' and is_forced is True:
158 halt_message = 'Branch `{}` changes rejected by rule {}. ' \
159 'FORCE PUSH FORBIDDEN.'.format(branch_name, rule)
156 halt_message = f'Branch `{branch_name}` changes rejected by rule {rule}. ' \
157 f'FORCE PUSH FORBIDDEN.'
160 158 else:
161 halt_message = 'Branch `{}` changes rejected by rule {}.'.format(
162 branch_name, rule)
159 halt_message = f'Branch `{branch_name}` changes rejected by rule {rule}.'
163 160
164 161 if halt_message:
165 162 _http_ret = HTTPBranchProtected(halt_message)
@@ -281,7 +278,7 b' def post_push(extras):'
281 278 # make lock is a tri state False, True, None. We only release lock on False
282 279 if extras.make_lock is False and not is_shadow_repo(extras):
283 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 282 output += msg
286 283
287 284 if extras.locked_by[0]:
@@ -299,12 +296,12 b' def post_push(extras):'
299 296 safe_str(extras.server_url), safe_str(extras.repository))
300 297
301 298 for branch_name in extras.new_refs['branches']:
302 output += 'RhodeCode: open pull request link: {}\n'.format(
303 tmpl.format(ref_type='branch', ref_name=safe_str(branch_name)))
299 pr_link = 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 302 for book_name in extras.new_refs['bookmarks']:
306 output += 'RhodeCode: open pull request link: {}\n'.format(
307 tmpl.format(ref_type='bookmark', ref_name=safe_str(book_name)))
303 pr_link = 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 306 hook_response = ''
310 307 if not is_shadow_repo(extras):
@@ -319,9 +316,7 b' def post_push(extras):'
319 316
320 317
321 318 def _locked_by_explanation(repo_name, user_name, reason):
322 message = (
323 'Repository `%s` locked by user `%s`. Reason:`%s`'
324 % (repo_name, user_name, reason))
319 message = f'Repository `{repo_name}` locked by user `{user_name}`. Reason:`{reason}`'
325 320 return message
326 321
327 322
@@ -25,6 +25,7 b' import traceback'
25 25 import threading
26 26 import socket
27 27 import msgpack
28 import gevent
28 29
29 30 from http.server import BaseHTTPRequestHandler
30 31 from socketserver import TCPServer
@@ -43,6 +44,23 b' log = logging.getLogger(__name__)'
43 44
44 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 64 def do_POST(self):
47 65 hooks_proto, method, extras = self._read_request()
48 66 log.debug('Handling HooksHttpHandler %s with %s proto', method, hooks_proto)
@@ -62,6 +80,7 b' class HooksHttpHandler(BaseHTTPRequestHa'
62 80 try:
63 81 hooks = Hooks(request=request, log_prefix='HOOKS: {} '.format(self.server.server_address))
64 82 result = self._call_hook_method(hooks, method, extras)
83
65 84 except Exception as e:
66 85 exc_tb = traceback.format_exc()
67 86 result = {
@@ -73,26 +92,30 b' class HooksHttpHandler(BaseHTTPRequestHa'
73 92
74 93 def _read_request(self):
75 94 length = int(self.headers['Content-Length'])
76 hooks_proto = self.headers.get('rc-hooks-protocol') or 'json.v1'
77 if hooks_proto == 'msgpack.v1':
95 # respect sent headers, fallback to OLD proto for compatability
96 hooks_proto = self.headers.get('rc-hooks-protocol') or self.JSON_HOOKS_PROTO
97 if hooks_proto == self.MSGPACK_HOOKS_PROTO:
78 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 101 else:
81 102 body = self.rfile.read(length)
82 data = json.loads(body)
103 data = self.deserialize_data(body)
83 104
84 105 return hooks_proto, data['method'], data['extras']
85 106
86 107 def _write_response(self, hooks_proto, result):
87 108 self.send_response(200)
88 if hooks_proto == 'msgpack.v1':
109 if hooks_proto == self.MSGPACK_HOOKS_PROTO:
89 110 self.send_header("Content-type", "application/msgpack")
90 111 self.end_headers()
91 self.wfile.write(msgpack.packb(result))
112 data = self.serialize_data(result)
113 self.wfile.write(data)
92 114 else:
93 115 self.send_header("Content-type", "text/json")
94 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 120 def _call_hook_method(self, hooks, method, extras):
98 121 try:
@@ -110,7 +133,7 b' class HooksHttpHandler(BaseHTTPRequestHa'
110 133 message = format % args
111 134
112 135 log.debug(
113 "HOOKS: %s - - [%s] %s", self.client_address,
136 "HOOKS: client=%s - - [%s] %s", self.client_address,
114 137 self.log_date_time_string(), message)
115 138
116 139
@@ -133,18 +156,25 b' class ThreadedHookCallbackDaemon(object)'
133 156 _callback_thread = None
134 157 _daemon = None
135 158 _done = False
159 use_gevent = False
136 160
137 161 def __init__(self, txn_id=None, host=None, port=None):
138 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 170 def __enter__(self):
141 171 log.debug('Running `%s` callback daemon', self.__class__.__name__)
142 self._run()
172 self._run_func()
143 173 return self
144 174
145 175 def __exit__(self, exc_type, exc_val, exc_tb):
146 176 log.debug('Exiting `%s` callback daemon', self.__class__.__name__)
147 self._stop()
177 self._stop_func()
148 178
149 179 def _prepare(self, txn_id=None, host=None, port=None):
150 180 raise NotImplementedError()
@@ -155,6 +185,12 b' class ThreadedHookCallbackDaemon(object)'
155 185 def _stop(self):
156 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 195 class HttpHooksCallbackDaemon(ThreadedHookCallbackDaemon):
160 196 """
@@ -167,6 +203,8 b' class HttpHooksCallbackDaemon(ThreadedHo'
167 203 # request and wastes cpu at all other times.
168 204 POLL_INTERVAL = 0.01
169 205
206 use_gevent = False
207
170 208 @property
171 209 def _hook_prefix(self):
172 210 return 'HOOKS: {} '.format(self.hooks_uri)
@@ -203,7 +241,7 b' class HttpHooksCallbackDaemon(ThreadedHo'
203 241 self._daemon.request = get_current_request()
204 242
205 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 245 callback_thread = threading.Thread(
208 246 target=self._daemon.serve_forever,
209 247 kwargs={'poll_interval': self.POLL_INTERVAL})
@@ -211,6 +249,19 b' class HttpHooksCallbackDaemon(ThreadedHo'
211 249 callback_thread.start()
212 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 265 def _stop(self):
215 266 log.debug("Waiting for background thread to finish.")
216 267 self._daemon.shutdown()
@@ -225,6 +276,29 b' class HttpHooksCallbackDaemon(ThreadedHo'
225 276
226 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 303 def get_txn_id_data_path(txn_id):
230 304 import rhodecode
General Comments 0
You need to be logged in to leave comments. Login now