summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--gitautodeploy/cli/config.py4
-rw-r--r--gitautodeploy/gitautodeploy.py182
-rw-r--r--gitautodeploy/httpserver.py9
-rw-r--r--gitautodeploy/wsserver.py81
-rw-r--r--platforms/debian/stdeb/control2
5 files changed, 167 insertions, 111 deletions
diff --git a/gitautodeploy/cli/config.py b/gitautodeploy/cli/config.py
index f87057e..6cd62be 100644
--- a/gitautodeploy/cli/config.py
+++ b/gitautodeploy/cli/config.py
@@ -28,7 +28,9 @@ def get_config_defaults():
config['web-ui'] = {
'enabled': False,
- 'remote-whitelist': ['127.0.0.1']
+ 'remote-whitelist': ['127.0.0.1'],
+ 'ws-host': '0.0.0.0',
+ 'ws-port': 9000
}
return config
diff --git a/gitautodeploy/gitautodeploy.py b/gitautodeploy/gitautodeploy.py
index c7ebf3a..4d4f838 100644
--- a/gitautodeploy/gitautodeploy.py
+++ b/gitautodeploy/gitautodeploy.py
@@ -14,17 +14,19 @@ class LogInterface(object):
def flush(self):
pass
+from .wsserver import WebSocketClientHandlerFactory
+from .httpserver import WebhookRequestHandlerFactory
+
class GitAutoDeploy(object):
_instance = None
_http_server = None
- _ws_server = None
_config = {}
- _port = None
_pid = None
_event_store = None
_default_stdout = None
_default_stderr = None
_startup_event = None
+ _ws_clients = []
def __new__(cls, *args, **kwargs):
"""Overload constructor to enable singleton access"""
@@ -40,7 +42,8 @@ class GitAutoDeploy(object):
self._event_store = EventStore()
self._event_store.register_observer(self)
- # Create a startup event that can hold status and any error messages from the startup process
+ # Create a startup event that can hold status and any error messages
+ # from the startup process
self._startup_event = StartupEvent()
self._event_store.register_action(self._startup_event)
@@ -53,7 +56,7 @@ class GitAutoDeploy(object):
from .wrappers import GitWrapper
logger = logging.getLogger()
- if not 'repositories' in self._config:
+ if 'repositories' not in self._config:
return
# Iterate over all configured repositories
@@ -82,8 +85,8 @@ class GitAutoDeploy(object):
logger = logging.getLogger()
for repository in self._config['repositories']:
-
- if not 'url' in repository:
+
+ if 'url' not in repository:
continue
logger.info("Scanning repository: %s" % repository['url'])
@@ -116,7 +119,8 @@ class GitAutoDeploy(object):
try:
os.remove(self._config['pidfilepath'])
except OSError as e:
- if e.errno != errno.ENOENT: # errno.ENOENT = no such file or directory
+ # errno.ENOENT = no such file or directory
+ if e.errno != errno.ENOENT:
raise
@staticmethod
@@ -136,7 +140,7 @@ class GitAutoDeploy(object):
try:
# Spawn second child
pid = os.fork()
-
+
except OSError as e:
raise Exception("%s [%d]" % (e.strerror, e.errno))
@@ -152,7 +156,11 @@ class GitAutoDeploy(object):
return 0
def update(self, *args, **kwargs):
- pass
+ import json
+ data = self._event_store.dict_repr()
+ json_data = json.dumps(data).encode('utf-8')
+ for client in self._ws_clients:
+ client.notify_refresh(json_data)
def setup(self, config):
"""Setup an instance of GAD based on the provided config object."""
@@ -161,12 +169,6 @@ class GitAutoDeploy(object):
import os
import logging
from .lock import Lock
- from .httpserver import WebhookRequestHandlerFactory
-
- try:
- from BaseHTTPServer import HTTPServer
- except ImportError as e:
- from http.server import HTTPServer
# This solves https://github.com/olipo186/Git-Auto-Deploy/issues/118
try:
@@ -231,8 +233,6 @@ class GitAutoDeploy(object):
if 'daemon-mode' in self._config and self._config['daemon-mode']:
self._startup_event.log_info('Starting Git Auto Deploy in daemon mode')
GitAutoDeploy.create_daemon()
- else:
- self._startup_event.log_info('Git Auto Deploy started')
self._pid = os.getpid()
self.create_pid_file()
@@ -245,6 +245,27 @@ class GitAutoDeploy(object):
Lock(os.path.join(repo_config['path'], 'status_running')).clear()
Lock(os.path.join(repo_config['path'], 'status_waiting')).clear()
+ if 'daemon-mode' not in self._config or not self._config['daemon-mode']:
+ self._startup_event.log_info('Git Auto Deploy started')
+
+
+ def serve_http(self):
+ """Starts a HTTP server that listens for webhook requests and serves the web ui."""
+ import sys
+ import socket
+ import logging
+ import os
+ from .events import SystemEvent, StartupEvent
+
+ try:
+ from BaseHTTPServer import HTTPServer
+ except ImportError as e:
+ from http.server import HTTPServer
+
+ start_http_event = StartupEvent()
+ self._event_store.register_action(start_http_event)
+
+ # Setup
try:
# Create web hook request handler class
@@ -255,22 +276,6 @@ class GitAutoDeploy(object):
self._config['port']),
WebhookRequestHandler)
- # Start a web socket server if the web UI is enabled
- #if self._config['web-ui']['enabled']:
-
- # try:
- # from SimpleWebSocketServer import SimpleWebSocketServer
- # from wsserver import WebSocketClientHandler
-
- # # Create web socket server
- # self._ws_server = SimpleWebSocketServer(self._config['ws-host'], self._config['ws-port'], WebSocketClientHandler)
-
- # sa = self._ws_server.socket.getsockname()
- # self._startup_event.log_info("Listening for web socket on %s port %s" % (sa[0], sa[1]))
-
- # except ImportError as e:
- # self._startup_event.log_error("Unable to start web socket server due to a too old version of Python. Version => 2.7.9 is required.")
-
# Setup SSL for HTTP server
if 'ssl' in self._config and self._config['ssl']:
import ssl
@@ -279,25 +284,16 @@ class GitAutoDeploy(object):
certfile=os.path.expanduser(self._config['ssl-pem']),
server_side=True)
sa = self._http_server.socket.getsockname()
- self._startup_event.log_info("Listening for http connections on %s port %s" % (sa[0], sa[1]))
- self._startup_event.address = sa[0]
- self._startup_event.port = sa[1]
- self._startup_event.notify()
-
- # Actual port bound to (nessecary when OS picks randomly free port)
- self._port = sa[1]
+ start_http_event.log_info("Listening for http connections on %s port %s" % (sa[0], sa[1]))
+ start_http_event.address = sa[0]
+ start_http_event.port = sa[1]
+ start_http_event.notify()
except socket.error as e:
- self._startup_event.log_critical("Error on socket: %s" % e)
+ start_http_event.log_critical("Error on socket: %s" % e)
sys.exit(1)
- def serve_http(self):
- import sys
- import socket
- import logging
- import os
- from .events import SystemEvent
-
+ # Run forever
try:
self._http_server.serve_forever()
@@ -306,7 +302,7 @@ class GitAutoDeploy(object):
self._event_store.register_action(event)
event.log_critical("Error on socket: %s" % e)
sys.exit(1)
-
+
except KeyboardInterrupt as e:
event = SystemEvent()
self._event_store.register_action(event)
@@ -317,12 +313,45 @@ class GitAutoDeploy(object):
pass
def serve_ws(self):
- if not self._ws_server:
+ """Start a web socket server, used by the web UI to get notifications about updates."""
+
+ from .events import SystemEvent, StartupEvent
+ start_ws_event = StartupEvent()
+ self._event_store.register_action(start_ws_event)
+
+ # Start a web socket server if the web UI is enabled
+ if not self._config['web-ui']['enabled']:
return
- self._ws_server.serveforever()
+
+ try:
+ import sys
+ from autobahn.websocket import WebSocketServerProtocol, WebSocketServerFactory
+ from twisted.internet import reactor
+
+ # Create a WebSocketClientHandler instance
+ WebSocketClientHandler = WebSocketClientHandlerFactory(self._config, self._ws_clients, self._event_store)
+
+ uri = u"ws://%s:%s" % (self._config['web-ui']['ws-host'], self._config['web-ui']['ws-port'])
+ factory = WebSocketServerFactory(uri)
+ factory.protocol = WebSocketClientHandler
+ # factory.setProtocolOptions(maxConnections=2)
+
+ # note to self: if using putChild, the child must be bytes...
+ self._ws_server_port = reactor.listenTCP(self._config['web-ui']['ws-port'], factory)
+
+ start_ws_event.log_info("Listening for web socket connections on %s port %s" % (self._config['web-ui']['ws-host'], self._config['web-ui']['ws-port']))
+ start_ws_event.address = self._config['web-ui']['ws-host']
+ start_ws_event.port = self._config['web-ui']['ws-port']
+ start_ws_event.notify()
+
+ # Serve forever (until reactor.stop())
+ reactor.run(installSignalHandlers=False)
+
+ except ImportError:
+ self._startup_event.log_error("Unable to start web socket server due to missing dependency.")
def serve_forever(self):
- """Start listening for incoming requests."""
+ """Start HTTP and web socket servers."""
import sys
import socket
import logging
@@ -337,45 +366,24 @@ class GitAutoDeploy(object):
wwwroot = os.path.join(os.path.dirname(os.path.realpath(__file__)), "wwwroot")
os.chdir(wwwroot)
+ # Start HTTP server
t1 = threading.Thread(target=self.serve_http)
#t1.daemon = True
- t1.start()
+ # Start web socket server
+ t2 = threading.Thread(target=self.serve_ws)
+ #t2.daemon = True
- #t2 = threading.Thread(target=self.serve_ws)
- #t1.daemon = True
- #t2.start()
+ t1.start()
+ t2.start()
# Wait for thread to finish without blocking main thread
- while t1.isAlive:
+ while t1.is_alive():
t1.join(5)
# Wait for thread to finish without blocking main thread
- #while t2.isAlive:
- # t2.join(5)
-
-
- def handle_request(self):
- """Start listening for incoming requests."""
- import sys
- import socket
- from .events import SystemEvent
-
- try:
- self._http_server.handle_request()
-
- except socket.error as e:
- event = SystemEvent()
- self._event_store.register_action(event)
- event.log_critical("Error on socket: %s" % e)
- sys.exit(1)
-
- except KeyboardInterrupt as e:
- event = SystemEvent()
- self._event_store.register_action(event)
- event.log_info('Requested close by keyboard interrupt signal')
- self.stop()
- self.exit()
+ while t2.is_alive():
+ t2.join(5)
def signal_handler(self, signum, frame):
from .events import SystemEvent
@@ -407,12 +415,16 @@ class GitAutoDeploy(object):
# Shut down the underlying TCP server
self._http_server.shutdown()
+
# Close the socket
self._http_server.socket.close()
# Stop web socket server if running
- if self._ws_server is not None:
- self._ws_server.close()
+ try:
+ from twisted.internet import reactor
+ reactor.callFromThread(reactor.stop)
+ except ImportError:
+ pass
def exit(self):
import sys
@@ -428,7 +440,7 @@ class GitAutoDeploy(object):
sys.stdout = self._default_stdout
sys.stderr = self._default_stderr
- sys.exit(0)
+ #sys.exit(0)
def main():
import signal
diff --git a/gitautodeploy/httpserver.py b/gitautodeploy/httpserver.py
index 5a06498..e830fe6 100644
--- a/gitautodeploy/httpserver.py
+++ b/gitautodeploy/httpserver.py
@@ -1,4 +1,7 @@
-from .parsers import CodingRequestParser, GitLabCIRequestParser, GitLabRequestParser, GitHubRequestParser, BitBucketRequestParser, GenericRequestParser
+from .parsers import CodingRequestParser, GitLabCIRequestParser
+from .parsers import GitLabRequestParser, GitHubRequestParser
+from .parsers import BitBucketRequestParser, GenericRequestParser
+
class WebbhookRequestProcessor(object):
@@ -70,7 +73,7 @@ class WebbhookRequestProcessor(object):
# In case there is no path configured for the repository, no pull will
# be made.
- if not 'path' in repo_config:
+ if 'path' not in repo_config:
res = GitWrapper.deploy(repo_config)
repo_result['deploy'] = res
result.append(repo_result)
@@ -183,7 +186,7 @@ class WebhookRequestFilter(object):
continue
# If the filter value is set to True. the filter
- # will pass regardless of the actual value
+ # will pass regardless of the actual value
if filter_value == True:
continue
diff --git a/gitautodeploy/wsserver.py b/gitautodeploy/wsserver.py
index 3198c18..05e2b61 100644
--- a/gitautodeploy/wsserver.py
+++ b/gitautodeploy/wsserver.py
@@ -1,21 +1,60 @@
-from SimpleWebSocketServer import WebSocket
-
-clients = []
-class WebSocketClientHandler(WebSocket):
-
- def handleMessage(self):
- for client in clients:
- if client != self:
- client.sendMessage(self.address[0] + u' - ' + self.data)
-
- def handleConnected(self):
- print (self.address, 'connected')
- for client in clients:
- client.sendMessage(self.address[0] + u' - connected')
- clients.append(self)
-
- def handleClose(self):
- clients.remove(self)
- print (self.address, 'closed')
- for client in clients:
- client.sendMessage(self.address[0] + u' - disconnected')
+try:
+ from autobahn.websocket import WebSocketServerProtocol
+except ImportError:
+ WebSocketServerProtocol = object
+
+def WebSocketClientHandlerFactory(config, clients, event_store):
+ """Factory method for webhook request handler class"""
+
+ class WebSocketClientHandler(WebSocketServerProtocol, object):
+ from .events import SystemEvent
+
+ def __init__(self, *args, **kwargs):
+ self._config = config
+ self.clients = clients
+ self.event_store = event_store
+ import logging
+ self.logger = logging.getLogger()
+ super(WebSocketClientHandler, self).__init__(*args, **kwargs)
+
+ def onConnect(self, request):
+ self.logger.info("Client connecting: {0}".format(request.peer))
+
+ # Validate the request
+ if not self._config['web-ui']['enabled'] or not self.peer.host in self._config['web-ui']['remote-whitelist']:
+ self.sendClose()
+ logger.info("Unautorized connection attempt from %s" % self.peer.host)
+ return
+
+ self.clients.append(self)
+
+ def onOpen(self):
+ self.logger.info("WebSocket connection open.")
+
+ def onMessage(self, payload, isBinary):
+ self.logger.info("WebSocket connection open.")
+ if isBinary:
+ self.logger.info("Binary message received: {0} bytes".format(len(payload)))
+ else:
+ self.logger.info("Text message received: {0}".format(payload.decode('utf8')))
+
+ for client in self.clients:
+ client.sendMessage(payload, isBinary)
+
+ # echo back message verbatim
+ self.sendMessage(payload, isBinary)
+
+ def onClose(self, wasClean, code, reason):
+ self.logger.info("WebSocket connection closed: {0}".format(reason))
+
+ if self in self.clients:
+ self.clients.remove(self)
+
+ def notify_refresh(self, payload):
+ import json
+ self.sendMessage(json.dumps({
+ "event": "refresh",
+ "payload": payload
+ }))
+
+ return WebSocketClientHandler
diff --git a/platforms/debian/stdeb/control b/platforms/debian/stdeb/control
index 2f70137..c7b8e16 100644
--- a/platforms/debian/stdeb/control
+++ b/platforms/debian/stdeb/control
@@ -2,7 +2,7 @@ Source: git-auto-deploy
Maintainer: Oliver Poignant <oliver@poignant.se>
Section: python
Priority: optional
-Build-Depends: python-all (>= 2.6.6-3), debhelper (>= 7), python-setuptools (>= 0.6), git (>= 2), python-lockfile (>= 0.8)
+Build-Depends: python-all (>= 2.6.6-3), debhelper (>= 7), python-setuptools (>= 0.6), git (>= 2), python-lockfile (>= 0.8), python-twisted (>= 13.2.0), python-autobahn (>= 0.5.14)
Standards-Version: 3.9.1