diff options
Diffstat (limited to 'gitautodeploy')
-rw-r--r-- | gitautodeploy/cli/config.py | 5 | ||||
-rw-r--r-- | gitautodeploy/events.py | 65 | ||||
-rw-r--r-- | gitautodeploy/gitautodeploy.py | 59 | ||||
-rw-r--r-- | gitautodeploy/httpserver.py | 53 | ||||
-rw-r--r-- | gitautodeploy/wwwroot/index.html | 1 |
5 files changed, 143 insertions, 40 deletions
diff --git a/gitautodeploy/cli/config.py b/gitautodeploy/cli/config.py index 69491c5..e302da4 100644 --- a/gitautodeploy/cli/config.py +++ b/gitautodeploy/cli/config.py @@ -27,7 +27,10 @@ def get_config_defaults(): config['log-test-case'] = False config['log-test-case-dir'] = None - config['remote-whitelist'] = ['127.0.0.1'] + config['web-ui'] = { + 'enabled': False, + 'remote-whitelist': ['127.0.0.1'] + } return config diff --git a/gitautodeploy/events.py b/gitautodeploy/events.py index b021165..bbf93a2 100644 --- a/gitautodeploy/events.py +++ b/gitautodeploy/events.py @@ -1,4 +1,4 @@ -class Action(object): +class SystemEvent(object): def __init__(self, name=None): import logging @@ -7,16 +7,25 @@ class Action(object): self.hub = None self.messages = [] self.name = name + self.id = None + self.waiting = None + self.success = None def __repr__(self): - if self.name: - return "<Action:%s>" % self.name + if self.id: + return "<SystemEvent:%s>" % self.id else: - return "<Action>" + return "<SystemEvent>" def dict_repr(self): + from time import time return { - "messages": self.messages + "id": self.id, + "type": type(self).__name__, + "timestamp": time(), + "messages": self.messages, + "waiting": self.waiting, + "success": self.success } def register_hub(self, hub): @@ -26,6 +35,21 @@ class Action(object): self.messages.append(message) self.hub.update_action(self, message) + def notify(self): + self.hub.notify(self) + + def set_id(self, id): + self.id = id + + def get_id(self): + return self.id + + def set_waiting(self, value): + self.waiting = value + + def set_success(self, value): + self.success = value + def log_debug(self, message): self.logger.debug(message) self.register_message(message, "DEBUG") @@ -49,7 +73,7 @@ class Action(object): self.hub.update_action(self) -class WebhookAction(Action): +class WebhookAction(SystemEvent): """Represents a webhook request event and keeps a copy of all incoming and outgoing data for monitoring purposes.""" def __init__(self, client_address, request_headers, request_body): @@ -63,8 +87,27 @@ class WebhookAction(Action): def dict_repr(self): data = super(WebhookAction, self).dict_repr() - data['request_headers'] = self.request_headers - data['request_body'] = self.request_body + data['client-address'] = self.client_address[0] + data['client-port'] = self.client_address[1] + data['request-headers'] = self.request_headers + data['request-body'] = self.request_body + return data + + +class StartupEvent(SystemEvent): + + def __init__(self, address=None, port=None): + self.address = address + self.port = port + super(StartupEvent, self).__init__() + + def __repr__(self): + return "<StartupEvent>" + + def dict_repr(self): + data = super(StartupEvent, self).dict_repr() + data['address'] = self.address + data['port'] = self.port return data @@ -73,6 +116,7 @@ class EventStore(object): def __init__(self): self.actions = [] self.observers = [] + self.next_id = 0 def register_observer(self, observer): self.observers.append(observer) @@ -86,7 +130,9 @@ class EventStore(object): observer.update(*args, **kwargs) def register_action(self, action): + action.set_id(self.next_id) action.register_hub(self) + self.next_id = self.next_id + 1 self.actions.append(action) self.update_observers(action) @@ -94,6 +140,9 @@ class EventStore(object): if len(self.actions) > 100: self.actions.pop(0) + def notify(self, event): + self.update_observers(event=event) + def update_action(self, action, message=None): self.update_observers(action, message) diff --git a/gitautodeploy/gitautodeploy.py b/gitautodeploy/gitautodeploy.py index 167fae8..c0d5b21 100644 --- a/gitautodeploy/gitautodeploy.py +++ b/gitautodeploy/gitautodeploy.py @@ -18,6 +18,9 @@ class GitAutoDeploy(object): _config = {} _port = None _pid = None + _event_store = None + _default_stdout = None + _default_stderr = None def __new__(cls, *args, **kwargs): """Overload constructor to enable singleton access""" @@ -227,11 +230,8 @@ class GitAutoDeploy(object): return 0 - def update(self, action, message=None): + def update(self, *args, **kwargs): pass - #print "%s was updated" % action, -# if message: -# print "Message: %s" % message def setup(self, config): """Setup an instance of GAD based on the provided config object.""" @@ -242,7 +242,7 @@ class GitAutoDeploy(object): import logging from lock import Lock from httpserver import WebhookRequestHandlerFactory - from events import EventStore + from events import EventStore, StartupEvent # This solves https://github.com/olipo186/Git-Auto-Deploy/issues/118 try: @@ -257,6 +257,12 @@ class GitAutoDeploy(object): # Attatch config values to this instance self._config = config + self._event_store = EventStore() + self._event_store.register_observer(self) + + startup_event = StartupEvent() + self._event_store.register_action(startup_event) + # Set up logging logger = logging.getLogger() logFormatter = logging.Formatter("%(asctime)s [%(levelname)-5.5s] %(message)s") @@ -290,11 +296,11 @@ class GitAutoDeploy(object): logger.addHandler(fileHandler) if 'ssh-keyscan' in self._config and self._config['ssh-keyscan']: - logger.info('Scanning repository hosts for ssh keys...') + startup_event.log_info('Scanning repository hosts for ssh keys...') self.ssh_key_scan() if 'force' in self._config and self._config['force']: - logger.info('Attempting to kill any other process currently occupying port %s' % self._config['port']) + startup_event.log_info('Attempting to kill any other process currently occupying port %s' % self._config['port']) self.kill_conflicting_processes() # Clone all repos once initially @@ -309,10 +315,10 @@ class GitAutoDeploy(object): sys.stderr = LogInterface(logger.error) if 'daemon-mode' in self._config and self._config['daemon-mode']: - logger.info('Starting Git Auto Deploy in daemon mode') + startup_event.log_info('Starting Git Auto Deploy in daemon mode') GitAutoDeploy.create_daemon() else: - logger.info('Git Auto Deploy started') + startup_event.log_info('Git Auto Deploy started') self._pid = os.getpid() self.create_pid_file() @@ -327,11 +333,8 @@ class GitAutoDeploy(object): try: - event_store = EventStore() - event_store.register_observer(self) - # Create web hook request handler class - WebhookRequestHandler = WebhookRequestHandlerFactory(self._config, event_store) + WebhookRequestHandler = WebhookRequestHandlerFactory(self._config, self._event_store) self._server = HTTPServer((self._config['host'], self._config['port']), @@ -344,14 +347,16 @@ class GitAutoDeploy(object): certfile=os.path.expanduser(self._config['ssl-pem']), server_side=True) sa = self._server.socket.getsockname() - logger.info("Listening on %s port %s", sa[0], sa[1]) + startup_event.log_info("Listening on %s port %s" % (sa[0], sa[1])) + startup_event.address = sa[0] + startup_event.port = sa[1] + startup_event.notify() # Actual port bound to (nessecary when OS picks randomly free port) self._port = sa[1] except socket.error, e: - - logger.critical("Error on socket: %s" % e) + startup_event.log_critical("Error on socket: %s" % e) GitAutoDeploy.debug_diagnosis(self._config['port']) sys.exit(1) @@ -361,6 +366,15 @@ class GitAutoDeploy(object): import sys import socket import logging + import os + from events import SystemEvent + + # Add script dir to sys path, allowing us to import sub modules even after changing cwd + sys.path.insert(1, os.path.dirname(os.path.realpath(__file__))) + + # Set CWD to public www folder. This makes the http server serve files from the wwwroot directory. + wwwroot = os.path.join(os.path.dirname(os.path.realpath(__file__)), "wwwroot") + os.chdir(wwwroot) # Set up logging logger = logging.getLogger() @@ -370,10 +384,16 @@ class GitAutoDeploy(object): except socket.error, e: logger.critical("Error on socket: %s" % e) + event = SystemEvent() + self._event_store.register_action(event) + event.log_critical("Error on socket: %s" % e) sys.exit(1) except KeyboardInterrupt, e: logger.info('Requested close by keyboard interrupt signal') + event = SystemEvent() + self._event_store.register_action(event) + event.log_info('Requested close by keyboard interrupt signal') self.stop() self.exit() @@ -404,6 +424,7 @@ class GitAutoDeploy(object): self._server.socket.close() def signal_handler(self, signum, frame): + from events import SystemEvent import logging logger = logging.getLogger() self.stop() @@ -415,9 +436,15 @@ class GitAutoDeploy(object): elif signum == 2: logger.info('Requested close by keyboard interrupt signal') + event = SystemEvent() + self._event_store.register_action(event) + event.log_info('Requested close by keyboard interrupt signal') elif signum == 6: logger.info('Requested close by SIGABRT (process abort signal). Code 6.') + event = SystemEvent() + self._event_store.register_action(event) + event.log_info('Requested close by SIGABRT (process abort signal). Code 6.') self.exit() diff --git a/gitautodeploy/httpserver.py b/gitautodeploy/httpserver.py index 67e9744..38f28ff 100644 --- a/gitautodeploy/httpserver.py +++ b/gitautodeploy/httpserver.py @@ -255,8 +255,9 @@ class WebhookRequestFilter(object): def WebhookRequestHandlerFactory(config, event_store): """Factory method for webhook request handler class""" + from SimpleHTTPServer import SimpleHTTPRequestHandler - class WebhookRequestHandler(BaseHTTPRequestHandler, object): + class WebhookRequestHandler(SimpleHTTPRequestHandler, object): """Extends the BaseHTTPRequestHandler class and handles the incoming HTTP requests.""" @@ -265,20 +266,36 @@ def WebhookRequestHandlerFactory(config, event_store): self.event_store = event_store super(WebhookRequestHandler, self).__init__(*args, **kwargs) + def end_headers (self): + self.send_header('Access-Control-Allow-Origin', '*') + SimpleHTTPRequestHandler.end_headers(self) + + def do_HEAD(self): + import json + + if not self._config['web-ui']['enabled'] or not self.client_address[0] in self._config['web-ui']['remote-whitelist']: + self.send_error(403) + return + + return SimpleHTTPRequestHandler.do_HEAD(self) + def do_GET(self): import json - if not self.client_address[0] in self._config['remote-whitelist']: + if not self._config['web-ui']['enabled'] or not self.client_address[0] in self._config['web-ui']['remote-whitelist']: self.send_error(403) return - data = self.event_store.dict_repr() + if self.path == "/api/status": + data = self.event_store.dict_repr() + self.send_response(200, 'OK') + self.send_header('Content-type', 'application/json') + self.end_headers() + self.wfile.write(json.dumps(data)) + self.wfile.close() + return - self.send_response(200, 'OK') - self.send_header('Content-type', 'application/json') - self.end_headers() - self.wfile.write(json.dumps(data)) - self.wfile.close() + return SimpleHTTPRequestHandler.do_GET(self) def do_POST(self): """Invoked on incoming POST requests""" @@ -296,7 +313,8 @@ def WebhookRequestHandlerFactory(config, event_store): request_headers = dict(self.headers) request_headers = dict((k.lower(), v) for k, v in request_headers.iteritems()) - action = WebhookAction(self.client_address, request_body, request_headers) + action = WebhookAction(self.client_address, request_headers, request_body) + action.set_waiting(True) event_store.register_action(action) action.log_info('Incoming request from %s:%s' % (self.client_address[0], self.client_address[1])) @@ -361,7 +379,7 @@ def WebhookRequestHandlerFactory(config, event_store): action.log_info("Filter does not match") return - action.log_info("Deploying") + action.log_info("Executing deploy commands") # Schedule the execution of the webhook (git pull and trigger deploy etc) request_processor.execute_webhook(repo_configs, request_headers, request_body, action) @@ -374,12 +392,16 @@ def WebhookRequestHandlerFactory(config, event_store): 'deploy': 'echo test!' } - action.log_info("Done") + action.log_info("Deploy commands were executed") + action.set_success(True) + action.update() except ValueError, e: self.send_error(400, 'Unprocessable request') action.log_warning('Unable to process incoming request from %s:%s' % (self.client_address[0], self.client_address[1])) test_case['expected']['status'] = 400 + action.set_success(False) + action.update() return except Exception, e: @@ -389,6 +411,8 @@ def WebhookRequestHandlerFactory(config, event_store): test_case['expected']['status'] = 500 action.log_warning("Unable to process request") + action.set_success(False) + action.update() raise e @@ -398,6 +422,9 @@ def WebhookRequestHandlerFactory(config, event_store): if 'log-test-case' in self._config and self._config['log-test-case']: self.save_test_case(test_case) + action.set_waiting(False) + action.update() + def log_message(self, format, *args): """Overloads the default message logging method to allow messages to go through our custom logger instead.""" @@ -428,7 +455,3 @@ def WebhookRequestHandlerFactory(config, event_store): return WebhookRequestHandler -class WebhookRequestHandler(BaseHTTPRequestHandler): - """Extends the BaseHTTPRequestHandler class and handles the incoming - HTTP requests.""" - diff --git a/gitautodeploy/wwwroot/index.html b/gitautodeploy/wwwroot/index.html new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/gitautodeploy/wwwroot/index.html @@ -0,0 +1 @@ + |