Skip to content

Instantly share code, notes, and snippets.

@caseyamcl
Created July 31, 2014 13:53
Show Gist options
  • Save caseyamcl/eaf054e2e0af0ca63f71 to your computer and use it in GitHub Desktop.
Save caseyamcl/eaf054e2e0af0ca63f71 to your computer and use it in GitHub Desktop.
BB Receiver Public GIST
#!/usr/bin/env python
# -------------------------------------------------------------------
#
# Simple HTTP Server to listen to requests from Bitbucket
#
# @author Casey McLaughlin <caseyamcl@gmail.com>
#
# 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