##// END OF EJS Templates
audit-logger: use raw JSON with empty data to control unicode decode warnings....
audit-logger: use raw JSON with empty data to control unicode decode warnings. Passing in empty dict triggers warnings on sqlalchemy level that we're trying to pass in NON unicode into Unicode column.

File last commit:

r2135:d1c488ba default
r2184:c4cc6e2a default
Show More
hooks_daemon.py
241 lines | 7.2 KiB | text/x-python | PythonLexer
project: added all source files and assets
r1 # -*- coding: utf-8 -*-
license: updated copyright year to 2017
r1271 # Copyright (C) 2010-2017 RhodeCode GmbH
project: added all source files and assets
r1 #
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
import json
import logging
exception-handling: propagate hooks tracebacks to vcsserver for easier debugging.
r1458 import traceback
project: added all source files and assets
r1 import threading
from BaseHTTPServer import BaseHTTPRequestHandler
from SocketServer import TCPServer
dan
integrations: add integration support...
r411 import rhodecode
dan
db: move Session.remove to outer wsgi layer and also add it...
r669 from rhodecode.model import meta
pyramid: ported pyramid routing for events
r2016 from rhodecode.lib.base import bootstrap_request
project: added all source files and assets
r1 from rhodecode.lib import hooks_base
pyramid: ported pyramid routing for events
r2016 from rhodecode.lib.utils2 import AttributeDict
project: added all source files and assets
r1
log = logging.getLogger(__name__)
class HooksHttpHandler(BaseHTTPRequestHandler):
def do_POST(self):
method, extras = self._read_request()
try:
result = self._call_hook(method, extras)
except Exception as e:
exception-handling: propagate hooks tracebacks to vcsserver for easier debugging.
r1458 exc_tb = traceback.format_exc()
project: added all source files and assets
r1 result = {
'exception': e.__class__.__name__,
exception-handling: propagate hooks tracebacks to vcsserver for easier debugging.
r1458 'exception_traceback': exc_tb,
project: added all source files and assets
r1 'exception_args': e.args
}
self._write_response(result)
def _read_request(self):
length = int(self.headers['Content-Length'])
body = self.rfile.read(length).decode('utf-8')
data = json.loads(body)
return data['method'], data['extras']
def _write_response(self, result):
self.send_response(200)
self.send_header("Content-type", "text/json")
self.end_headers()
self.wfile.write(json.dumps(result))
def _call_hook(self, method, extras):
hooks = Hooks()
dan
db: move Session.remove to outer wsgi layer and also add it...
r669 try:
result = getattr(hooks, method)(extras)
finally:
meta.Session.remove()
project: added all source files and assets
r1 return result
def log_message(self, format, *args):
"""
core: removed pyro4 from Enterprise code. Fixes #5198
r1409 This is an overridden method of BaseHTTPRequestHandler which logs using
project: added all source files and assets
r1 logging library instead of writing directly to stderr.
"""
message = format % args
# TODO: mikhail: add different log levels support
log.debug(
"%s - - [%s] %s", self.client_address[0],
self.log_date_time_string(), message)
class DummyHooksCallbackDaemon(object):
def __init__(self):
self.hooks_module = Hooks.__module__
def __enter__(self):
log.debug('Running dummy hooks callback daemon')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
log.debug('Exiting dummy hooks callback daemon')
class ThreadedHookCallbackDaemon(object):
_callback_thread = None
_daemon = None
_done = False
def __init__(self):
self._prepare()
def __enter__(self):
self._run()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
hooks: added debug logs.
r2135 log.debug('Callback daemon exiting now...')
project: added all source files and assets
r1 self._stop()
def _prepare(self):
raise NotImplementedError()
def _run(self):
raise NotImplementedError()
def _stop(self):
raise NotImplementedError()
class HttpHooksCallbackDaemon(ThreadedHookCallbackDaemon):
"""
Context manager which will run a callback daemon in a background thread.
"""
hooks_uri = None
IP_ADDRESS = '127.0.0.1'
# From Python docs: Polling reduces our responsiveness to a shutdown
# request and wastes cpu at all other times.
POLL_INTERVAL = 0.1
def _prepare(self):
hooks: added debug logs.
r2135 log.debug("Preparing HTTP callback daemon and registering hook object")
project: added all source files and assets
r1
self._done = False
self._daemon = TCPServer((self.IP_ADDRESS, 0), HooksHttpHandler)
_, port = self._daemon.server_address
self.hooks_uri = '{}:{}'.format(self.IP_ADDRESS, port)
log.debug("Hooks uri is: %s", self.hooks_uri)
def _run(self):
log.debug("Running event loop of callback daemon in background thread")
callback_thread = threading.Thread(
target=self._daemon.serve_forever,
kwargs={'poll_interval': self.POLL_INTERVAL})
callback_thread.daemon = True
callback_thread.start()
self._callback_thread = callback_thread
def _stop(self):
log.debug("Waiting for background thread to finish.")
self._daemon.shutdown()
self._callback_thread.join()
self._daemon = None
self._callback_thread = None
Martin Bornhold
http: Remove default arguments from prepare callback function.
r961 def prepare_callback_daemon(extras, protocol, use_direct_calls):
project: added all source files and assets
r1 callback_daemon = None
if use_direct_calls:
callback_daemon = DummyHooksCallbackDaemon()
extras['hooks_module'] = callback_daemon.hooks_module
else:
core: removed pyro4 from Enterprise code. Fixes #5198
r1409 if protocol == 'http':
Martin Bornhold
vcs: Raise an error in case of unsupported protocol.
r589 callback_daemon = HttpHooksCallbackDaemon()
else:
log.error('Unsupported callback daemon protocol "%s"', protocol)
raise Exception('Unsupported callback daemon protocol.')
project: added all source files and assets
r1 extras['hooks_uri'] = callback_daemon.hooks_uri
extras['hooks_protocol'] = protocol
hooks: added debug logs.
r2135 log.debug('Prepared a callback daemon: %s', callback_daemon)
project: added all source files and assets
r1 return callback_daemon, extras
class Hooks(object):
"""
Exposes the hooks for remote call backs
"""
def repo_size(self, extras):
pyramid: ported pyramid routing for events
r2016 log.debug("Called repo_size of %s object", self)
project: added all source files and assets
r1 return self._call_hook(hooks_base.repo_size, extras)
def pre_pull(self, extras):
pyramid: ported pyramid routing for events
r2016 log.debug("Called pre_pull of %s object", self)
project: added all source files and assets
r1 return self._call_hook(hooks_base.pre_pull, extras)
def post_pull(self, extras):
pyramid: ported pyramid routing for events
r2016 log.debug("Called post_pull of %s object", self)
project: added all source files and assets
r1 return self._call_hook(hooks_base.post_pull, extras)
def pre_push(self, extras):
pyramid: ported pyramid routing for events
r2016 log.debug("Called pre_push of %s object", self)
project: added all source files and assets
r1 return self._call_hook(hooks_base.pre_push, extras)
def post_push(self, extras):
pyramid: ported pyramid routing for events
r2016 log.debug("Called post_push of %s object", self)
project: added all source files and assets
r1 return self._call_hook(hooks_base.post_push, extras)
def _call_hook(self, hook, extras):
extras = AttributeDict(extras)
events: make sure we propagate our dummy request with proper application_url....
r1960
events: make sure we propagate our dummy request with proper application_url....
r2017 extras.request = bootstrap_request(
application_url=extras['server_url'])
project: added all source files and assets
r1
try:
result = hook(extras)
except Exception as error:
exception-handling: propagate hooks tracebacks to vcsserver for easier debugging.
r1458 exc_tb = traceback.format_exc()
project: added all source files and assets
r1 log.exception('Exception when handling hook %s', hook)
error_args = error.args
return {
'status': 128,
'output': '',
'exception': type(error).__name__,
exception-handling: propagate hooks tracebacks to vcsserver for easier debugging.
r1458 'exception_traceback': exc_tb,
project: added all source files and assets
r1 'exception_args': error_args,
}
dan
integrations: add integration support...
r411 finally:
dan
pyro: remove db Session when callback finishes to avoid leaving...
r670 meta.Session.remove()
dan
integrations: add integration support...
r411
project: added all source files and assets
r1 return {
'status': result.status,
'output': result.output,
}
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
pass