summaryrefslogtreecommitdiffstats
path: root/gitautodeploy/httpserver.py
diff options
context:
space:
mode:
authorOliver Poignant <oliver@poignant.se>2016-03-06 23:10:30 +0100
committerOliver Poignant <oliver@poignant.se>2016-03-06 23:10:30 +0100
commitde6bc048c63976443fa6fbe00d6b65439bd54a82 (patch)
tree77bccdd71c36ec5ee1e597a995d1e39a309fd6eb /gitautodeploy/httpserver.py
parent9bb81f8c8e75278db86868575fc47394b0541eef (diff)
downloadGit-Auto-Deploy-de6bc048c63976443fa6fbe00d6b65439bd54a82.zip
Git-Auto-Deploy-de6bc048c63976443fa6fbe00d6b65439bd54a82.tar.gz
Git-Auto-Deploy-de6bc048c63976443fa6fbe00d6b65439bd54a82.tar.bz2
Added default config values and the ability to start the server without a config file. Moved HTTP server implementation to separate file.
Diffstat (limited to 'gitautodeploy/httpserver.py')
-rw-r--r--gitautodeploy/httpserver.py184
1 files changed, 184 insertions, 0 deletions
diff --git a/gitautodeploy/httpserver.py b/gitautodeploy/httpserver.py
new file mode 100644
index 0000000..a0ae4c5
--- /dev/null
+++ b/gitautodeploy/httpserver.py
@@ -0,0 +1,184 @@
+from BaseHTTPServer import BaseHTTPRequestHandler
+
+
+class FilterMatchError(Exception):
+ """Used to describe when a filter does not match a request."""
+ pass
+
+
+class WebhookRequestHandler(BaseHTTPRequestHandler):
+ """Extends the BaseHTTPRequestHandler class and handles the incoming
+ HTTP requests."""
+
+ def do_POST(self):
+ """Invoked on incoming POST requests"""
+ from threading import Timer
+ import logging
+
+ logger = logging.getLogger()
+
+ content_type = self.headers.getheader('content-type')
+ content_length = int(self.headers.getheader('content-length'))
+ request_body = self.rfile.read(content_length)
+
+ # Extract request headers and make all keys to lowercase (makes them easier to compare)
+ request_headers = dict(self.headers)
+ request_headers = dict((k.lower(), v) for k, v in request_headers.iteritems())
+
+ ServiceRequestParser = self.figure_out_service_from_request(request_headers, request_body)
+
+ self.send_response(200)
+ self.send_header('Content-type', 'text/plain')
+ self.end_headers()
+
+ # Unable to identify the source of the request
+ if not ServiceRequestParser:
+ logger.error('Unable to find appropriate handler for request. The source service is not supported.')
+ return
+
+ # Could be GitHubParser, GitLabParser or other
+ repo_configs, ref, action = ServiceRequestParser(self._config).get_repo_params_from_request(request_headers, request_body)
+
+ #if success:
+ # print "Successfullt handled request using %s" % ServiceHandler.__name__
+ #else:
+ # print "Unable to handle request using %s" % ServiceHandler.__name__
+
+ if len(repo_configs) == 0:
+ logger.warning('Unable to find any of the repository URLs in the config: %s' % ', '.join(repo_urls))
+ return
+
+ # Wait one second before we do git pull (why?)
+ Timer(1.0, self.process_repositories, (repo_configs,
+ ref,
+ action)).start()
+
+ def log_message(self, format, *args):
+ """Overloads the default message logging method to allow messages to
+ go through our custom logger instead."""
+ import logging
+ logger = logging.getLogger()
+ logger.info("%s - - [%s] %s\n" % (self.client_address[0],
+ self.log_date_time_string(),
+ format%args))
+
+ def figure_out_service_from_request(self, request_headers, request_body):
+ """Parses the incoming request and attempts to determine whether
+ it originates from GitHub, GitLab or any other known service."""
+ import json
+ import logging
+ import parsers
+
+ logger = logging.getLogger()
+ data = json.loads(request_body)
+
+ user_agent = 'user-agent' in request_headers and request_headers['user-agent']
+ content_type = 'content-type' in request_headers and request_headers['content-type']
+
+ # Assume GitLab if the X-Gitlab-Event HTTP header is set
+ if 'x-gitlab-event' in request_headers:
+
+ logger.info("Received event from GitLab")
+ return parsers.GitLabRequestParser
+
+ # Assume GitHub if the X-GitHub-Event HTTP header is set
+ elif 'x-github-event' in request_headers:
+
+ logger.info("Received event from GitHub")
+ return parsers.GitHubRequestParser
+
+ # Assume BitBucket if the User-Agent HTTP header is set to
+ # 'Bitbucket-Webhooks/2.0' (or something similar)
+ elif user_agent and user_agent.lower().find('bitbucket') != -1:
+
+ logger.info("Received event from BitBucket")
+ return parsers.BitBucketRequestParser
+
+ # Special Case for Gitlab CI
+ elif content_type == "application/json" and "build_status" in data:
+
+ logger.info('Received event from Gitlab CI')
+ return parsers.GitLabCIRequestParser
+
+ # This handles old GitLab requests and Gogs requests for example.
+ elif content_type == "application/json":
+
+ logger.info("Received event from unknown origin.")
+ return parsers.GenericRequestParser
+
+ logger.error("Unable to recognize request origin. Don't know how to handle the request.")
+ return
+
+
+ def process_repositories(self, repo_configs, ref, action):
+ import os
+ import time
+ import logging
+ from wrappers import GitWrapper
+ from lock import Lock
+
+ logger = logging.getLogger()
+
+ # Process each matching repository
+ for repo_config in repo_configs:
+
+ try:
+ # Verify that all filters matches the request if specified
+ if 'filters' in repo_config:
+ for filter in repo_config['filters']:
+ if filter['type'] == 'pull-request-filter':
+ if filter['ref'] == ref and filter['action'] == action:
+ continue
+ raise FilterMatchError()
+ else:
+ logger.error('Unrecognized filter: ' % filter)
+ raise FilterMatchError()
+
+ except FilterMatchError as e:
+ continue
+
+
+ # In case there is no path configured for the repository, no pull will
+ # be made.
+ if not 'path' in repo_config:
+ GitWrapper.deploy(repo_config)
+ continue
+
+ running_lock = Lock(os.path.join(repo_config['path'], 'status_running'))
+ waiting_lock = Lock(os.path.join(repo_config['path'], 'status_waiting'))
+ try:
+
+ # Attempt to obtain the status_running lock
+ while not running_lock.obtain():
+
+ # If we're unable, try once to obtain the status_waiting lock
+ if not waiting_lock.has_lock() and not waiting_lock.obtain():
+ logger.error("Unable to obtain the status_running lock nor the status_waiting lock. Another process is " +
+ "already waiting, so we'll ignore the request.")
+
+ # If we're unable to obtain the waiting lock, ignore the request
+ break
+
+ # Keep on attempting to obtain the status_running lock until we succeed
+ time.sleep(5)
+
+ n = 4
+ while 0 < n and 0 != GitWrapper.pull(repo_config):
+ n -= 1
+
+ if 0 < n:
+ GitWrapper.deploy(repo_config)
+
+ except Exception as e:
+ logger.error('Error during \'pull\' or \'deploy\' operation on path: %s' % repo_config['path'])
+ logger.error(e)
+
+ finally:
+
+ # Release the lock if it's ours
+ if running_lock.has_lock():
+ running_lock.release()
+
+ # Release the lock if it's ours
+ if waiting_lock.has_lock():
+ waiting_lock.release() \ No newline at end of file