Created
July 31, 2014 13:53
-
-
Save caseyamcl/eaf054e2e0af0ca63f71 to your computer and use it in GitHub Desktop.
BB Receiver Public GIST
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# ------------------------------------------------------------------- | |
# | |
# Simple HTTP Server to listen to requests from Bitbucket | |
# | |
# @author Casey McLaughlin <[email protected]> | |
# | |
# Usage: | |
# - To run the server: python server.py [/path/to/working/repo/directory] [port] [logfile] | |
# - Port is optional and defaults to 8085 | |
# - Logfile is optional and defaults to no logging | |
# | |
# ------------------------------------------------------------------- | |
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer | |
import ConfigParser, argparse, datetime | |
import sys, os, subprocess, json, logging, urllib, urlparse | |
# ------------------------------------------------------------------- | |
# Class for input errors | |
class AppError(Exception): | |
"""Base class for exceptions in this module.""" | |
pass | |
# ------------------------------------------------------------------- | |
# Define the only route | |
class MyHandler(BaseHTTPRequestHandler): | |
def do_GET(self): | |
self.send_response(415) | |
self.send_header("Content-type", 'text/plain') | |
self.end_headers() | |
self.wfile.write("Method not allowed") | |
return | |
def do_POST(self): | |
self.error_content_type = 'text/plain' | |
self.error_message_format = '''Error: %(message)s\n%(explain)s''' | |
try: | |
do_receive(self.rfile.read(int(self.headers['content-length'])), self.server.repo_path) | |
self.send_response(200) | |
self.send_header("Content-type", 'text/plain') | |
self.end_headers() | |
self.wfile.write('OK') | |
except ValueError as excp: | |
log_msg('HTTP 400 - Bad Request Received from client: {0}'.format(excp), level=logging.ERROR) | |
self.send_error(400,'Bad Request') | |
return | |
# ------------------------------------------------------------------- | |
# Run server function | |
def run_server(port, repo_path): | |
log_msg("Starting HTTP Server. Listening on {0}".format(port)) | |
try: | |
server = HTTPServer(('', port), MyHandler) | |
server.repo_path = repo_path | |
server.serve_forever() | |
except KeyboardInterrupt: | |
logging.info('^C received. Shutting down HTTP Server') | |
# ------------------------------------------------------------------- | |
# Receiver Logic | |
def do_receive(postData, repoWorkingDir): | |
# Check Git on every receive | |
check_git(repoWorkingDir) | |
# Decode POST Data | |
decodedPostData = dict(urlparse.parse_qsl(urllib.unquote(postData).decode('utf8'))) | |
# Data sent in the post request - Not doing anything with this for now | |
data = json.loads(decodedPostData['payload']) | |
# Messages | |
log_msg("Received {0} commit(s). Pulling in directory: {1}".format(len(data['commits']), repoWorkingDir)) | |
# Do a 'git pull' | |
try: | |
subprocess.check_call(['git', 'pull', '--all']) | |
except subprocess.CalledProcessError as cpe: | |
log_msg("GIT Pull failed: {0}".format(cpe.output), level=logging.ERROR) | |
# Tell Bitbucket OK | |
return "OK" | |
# ------------------------------------------------------------------- | |
# Check GIT function (at startup and for each post-receive hook) | |
def check_git(repoWorkingDir): | |
# Check directory exists | |
if not (os.path.isdir(repoWorkingDir)): | |
raise AppError("The repo working directory doesn't exist: {0}".format(repoWorkingDir)) | |
# Move to directory | |
os.chdir(repoWorkingDir) | |
# Open null so we don't print verbose output to the terminal | |
FNULL = open(os.devnull, 'w') | |
# Ensure the repo directory contains a GIT repository | |
try: | |
subprocess.check_call(['git', 'status'], stdout=FNULL, stderr=FNULL) | |
except subprocess.CalledProcessError as cpe: | |
raise AppError("No GIT working tree found in directory: {0}".format(repoWorkingDir)) | |
# ------------------------------------------------------------------- | |
# Log and print to screen function | |
def log_msg(msg,level=logging.INFO): | |
"""If logfile specified, log to that and output to terminal""" | |
logging.log(level, msg) | |
print msg | |
# ------------------------------------------------------------------- | |
# Setup parameters | |
def setup_params(): | |
# Parse arguments from CLI | |
parser = argparse.ArgumentParser(description='Bitbucket POST Webhook Receiver') | |
parser.add_argument('--logpath', '-l', help="Specify a path to log") | |
parser.add_argument('--port', '-p', type=int, default=8085, help="Specify a TCP port to listen on") | |
parser.add_argument('--config', '-c', help="Config file path") | |
parser.add_argument('--repopath', '-r', help="Path to repository to update") | |
args = parser.parse_args() | |
# If configuraiton defined, read from that first | |
if (args.config): | |
Config = ConfigParser.ConfigParser() | |
Config.read(args.config) | |
config = { | |
'logpath': Config.get('General', 'logpath'), | |
'port': Config.getint('General', 'port'), | |
'repopath': Config.get('General', 'repopath') | |
} | |
else: | |
config = {'logpath': None, 'port': None, 'repopath': None} | |
if args.logpath: | |
config['logpath'] = args.logpath | |
if args.repopath: | |
config['repopath'] = args.repopath | |
if args.port: | |
config['port'] = args.port | |
return config | |
# ------------------------------------------------------------------- | |
# Run the application | |
if __name__ == '__main__': | |
try: | |
# Get parameters | |
params = setup_params() | |
# Setup logging | |
if params['logpath']: | |
logging.basicConfig(filename=params['logpath'],level=logging.INFO, format='%(asctime)s - %(levelname)s => %(message)s') | |
# Check repo path | |
if not params['repopath']: | |
raise AppError("No repository path specified") | |
# Check GIT | |
check_git(params['repopath']) | |
# Run the app | |
run_server(params['port'], params['repopath']) | |
except AppError as excp: | |
log_msg("Fatal Error: {0}".format(excp), level=logging.ERROR) | |
sys.exit(1) | |
# EOF |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment