diff options
author | Oliver Poignant <oliver@poignant.se> | 2016-03-06 23:10:30 +0100 |
---|---|---|
committer | Oliver Poignant <oliver@poignant.se> | 2016-03-06 23:10:30 +0100 |
commit | de6bc048c63976443fa6fbe00d6b65439bd54a82 (patch) | |
tree | 77bccdd71c36ec5ee1e597a995d1e39a309fd6eb /gitautodeploy/httpserver.py | |
parent | 9bb81f8c8e75278db86868575fc47394b0541eef (diff) | |
download | Git-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.py | 184 |
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 |