diff options
author | Oliver Poignant <oliver@poignant.se> | 2017-01-07 00:03:33 +0100 |
---|---|---|
committer | Oliver Poignant <oliver@poignant.se> | 2017-01-07 00:03:33 +0100 |
commit | 4e7b30e9651c2b27843cf1a7b326a1f3ed8aa869 (patch) | |
tree | 85bab72772179693b01d957b9adf77af0811edde /gitautodeploy/httpserver.py | |
parent | c00f661b80f9029ee3644ba1b5ab7e60891bcf61 (diff) | |
download | Git-Auto-Deploy-4e7b30e9651c2b27843cf1a7b326a1f3ed8aa869.zip Git-Auto-Deploy-4e7b30e9651c2b27843cf1a7b326a1f3ed8aa869.tar.gz Git-Auto-Deploy-4e7b30e9651c2b27843cf1a7b326a1f3ed8aa869.tar.bz2 |
Separating HTTPS server instance
Diffstat (limited to 'gitautodeploy/httpserver.py')
-rw-r--r-- | gitautodeploy/httpserver.py | 327 |
1 files changed, 61 insertions, 266 deletions
diff --git a/gitautodeploy/httpserver.py b/gitautodeploy/httpserver.py index ea4c64c..ab50ecf 100644 --- a/gitautodeploy/httpserver.py +++ b/gitautodeploy/httpserver.py @@ -1,262 +1,4 @@ -from .parsers import CodingRequestParser, GitLabCIRequestParser -from .parsers import GitLabRequestParser, GitHubRequestParser -from .parsers import BitBucketRequestParser, GenericRequestParser - - -class WebbhookRequestProcessor(object): - - def get_service_handler(self, request_headers, request_body, action): - """Parses the incoming request and attempts to determine whether - it originates from GitHub, GitLab or any other known service.""" - import json - - payload = json.loads(request_body) - - if not isinstance(payload, dict): - raise ValueError("Invalid JSON object") - - 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 Coding if the X-Coding-Event HTTP header is set - if 'x-coding-event' in request_headers: - return CodingRequestParser - - # Assume GitLab if the X-Gitlab-Event HTTP header is set - elif 'x-gitlab-event' in request_headers: - - # Special Case for Gitlab CI - if content_type == "application/json" and "build_status" in payload: - return GitLabCIRequestParser - else: - return GitLabRequestParser - - # Assume GitHub if the X-GitHub-Event HTTP header is set - elif 'x-github-event' in request_headers: - - return 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: - - return BitBucketRequestParser - - # This handles old GitLab requests and Gogs requests for example. - elif content_type == "application/json": - - action.log_info("Received event from unknown origin.") - return GenericRequestParser - - action.log_error("Unable to recognize request origin. Don't know how to handle the request.") - return - - def execute_webhook(self, repo_configs, request_headers, request_body, action): - """Verify that the suggested repositories has matching settings and - issue git pull and/or deploy commands.""" - import os - import time - import logging - from .wrappers import GitWrapper - from .lock import Lock - import json - - logger = logging.getLogger() - payload = json.loads(request_body) - - result = [] - - # Process each matching repository - for repo_config in repo_configs: - - repo_result = {} - - # In case there is no path configured for the repository, no pull will - # be made. - if 'path' not in repo_config: - res = GitWrapper.deploy(repo_config) - repo_result['deploy'] = res - result.append(repo_result) - continue - - # If the path does not exist, a warning will be raised and no pull or - # deploy will be made. - if not os.path.isdir(repo_config['path']): - action.log_error("The repository '%s' does not exist locally. Make sure it was pulled properly without errors by reviewing the log." % repo_config['path']) - result.append(repo_result) - continue - - # If the path is not writable, a warning will be raised and no pull or - # deploy will be made. - if not os.access(repo_config['path'], os.W_OK): - action.log_error("The path '%s' is not writable. Make sure that GAD has write access to that path." % repo_config['path']) - result.append(repo_result) - 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(): - action.log_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 - res = None - while n > 0: - - # Attempt to pull up a maximum of 4 times - res = GitWrapper.pull(repo_config) - repo_result['git pull'] = res - - # Return code indicating success? - if res == 0: - break - - n -= 1 - - if 0 < n: - res = GitWrapper.deploy(repo_config) - repo_result['deploy'] = res - - #except Exception as e: - # logger.error('Error during \'pull\' or \'deploy\' operation on path: %s' % repo_config['path']) - # logger.error(e) - # raise 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() - - result.append(repo_result) - - action.log_info("Deploy commands were executed") - action.set_waiting(False) - action.set_success(True) - - return result - - -class WebhookRequestFilter(object): - - def passes_payload_filter(self, payload_filters, payload, action): - import logging - - logger = logging.getLogger() - - # At least one filter must match - for filter in payload_filters: - - # All options specified in the filter must match - for filter_key, filter_value in filter.items(): - - # Ignore filters with value None (let them pass) - if filter_value == None: - continue - - # Interpret dots in filter name as path notations - node_value = payload - for node_key in filter_key.split('.'): - - # If the path is not valid the filter does not match - if not node_key in node_value: - action.log_info("Filter '%s' does not match since the path is invalid" % (filter_key)) - - # Filter does not match, do not process this repo config - return False - - node_value = node_value[node_key] - - if filter_value == node_value: - continue - - # If the filter value is set to True. the filter - # will pass regardless of the actual value - if filter_value == True: - continue - - action.log_debug("Filter '%s' does not match ('%s' != '%s')" % (filter_key, filter_value, (str(node_value)[:75] + '..') if len(str(node_value)) > 75 else str(node_value))) - - # Filter does not match, do not process this repo config - return False - - # Filter does match, proceed - return True - - def passes_header_filter(self, header_filter, request_headers): - import logging - - logger = logging.getLogger() - - # At least one filter must match - for key in header_filter: - - # Verify that the request has the required header attribute - if key.lower() not in request_headers: - return False - - # "True" indicates that any header value is accepted - if header_filter[key] is True: - continue - - # Verify that the request has the required header value - if header_filter[key] != request_headers[key.lower()]: - return False - - # Filter does match, proceed - return True - - def apply_filters(self, repo_configs, request_headers, request_body, action): - """Verify that the suggested repositories has matching settings and - issue git pull and/or deploy commands.""" - import os - import time - import logging - from .wrappers import GitWrapper - from .lock import Lock - import json - - logger = logging.getLogger() - payload = json.loads(request_body) - - matches = [] - - # Process each matching repository - for repo_config in repo_configs: - - # Verify that all payload filters matches the request (if any payload filters are specified) - if 'payload-filter' in repo_config and not self.passes_payload_filter(repo_config['payload-filter'], payload, action): - - # Filter does not match, do not process this repo config - continue - - # Verify that all header filters matches the request (if any header filters are specified) - if 'header-filter' in repo_config and not self.passes_header_filter(repo_config['header-filter'], request_headers): - - # Filter does not match, do not process this repo config - continue - - matches.append(repo_config) - - return matches - - -def WebhookRequestHandlerFactory(config, event_store): +def WebhookRequestHandlerFactory(config, event_store, server_status, is_https=False): """Factory method for webhook request handler class""" try: from SimpleHTTPServer import SimpleHTTPRequestHandler @@ -269,7 +11,9 @@ def WebhookRequestHandlerFactory(config, event_store): def __init__(self, *args, **kwargs): self._config = config - self.event_store = event_store + self._event_store = event_store + self._server_status = server_status + self._is_https = is_https super(WebhookRequestHandler, self).__init__(*args, **kwargs) def end_headers (self): @@ -279,8 +23,25 @@ def WebhookRequestHandlerFactory(config, event_store): def do_HEAD(self): import json - if not self._config['web-ui-enabled'] or not self.client_address[0] in self._config['web-ui-whitelist']: - self.send_error(403) + if not self._config['web-ui-enabled']: + self.send_error(403, "Web UI is not enabled") + return + + if not self._is_https and self._config['web-ui-require-https']: + + # Attempt to redirect the request to HTTPS + server_status = self.get_server_status() + if 'https-uri' in server_status: + self.send_response(307) + self.send_header('Location', '%s%s' % (server_status['https-uri'], self.path)) + self.end_headers() + return + + self.send_error(403, "Web UI is only accessible through HTTPS") + return + + if not self.client_address[0] in self._config['web-ui-whitelist']: + self.send_error(403, "%s is not allowed access" % self.client_address[0]) return return SimpleHTTPRequestHandler.do_HEAD(self) @@ -288,15 +49,34 @@ def WebhookRequestHandlerFactory(config, event_store): def do_GET(self): import json - if not self._config['web-ui-enabled'] or not self.client_address[0] in self._config['web-ui-whitelist']: - self.send_error(403) + if not self._config['web-ui-enabled']: + self.send_error(403, "Web UI is not enabled") + return + + if not self._is_https and self._config['web-ui-require-https']: + + # Attempt to redirect the request to HTTPS + server_status = self.get_server_status() + if 'https-uri' in server_status: + self.send_response(307) + self.send_header('Location', '%s%s' % (server_status['https-uri'], self.path)) + self.end_headers() + return + + self.send_error(403, "Web UI is only accessible through HTTPS") + return + + if not self.client_address[0] in self._config['web-ui-whitelist']: + self.send_error(403, "%s is not allowed access" % self.client_address[0]) return if self.path == "/api/status": data = { - 'events': self.event_store.dict_repr(), - 'web-socket-port': self._config['web-ui-web-socket-port'] + 'events': self._event_store.dict_repr(), } + + data.update(self.get_server_status()) + self.send_response(200, 'OK') self.send_header('Content-type', 'application/json') self.end_headers() @@ -311,6 +91,7 @@ def WebhookRequestHandlerFactory(config, event_store): import logging import json from .events import WebhookAction + from .webhook import WebhookRequestFilter, WebbhookRequestProcessor import threading logger = logging.getLogger() @@ -452,5 +233,19 @@ def WebhookRequestHandlerFactory(config, event_store): file.write(json.dumps(test_case, sort_keys=True, indent=4)) file.close() + def get_server_status(self): + """Generate a copy of the server status object that contains the public IP or hostname.""" + server_status = {} + for item in self._server_status.items(): + key, value = item + public_host = self.headers.get('host').split(':')[0] + if key == 'http-uri': + server_status[key] = value.replace(self._config['http-host'], public_host) + if key == 'https-uri': + server_status[key] = value.replace(self._config['https-host'], public_host) + if key == 'wss-uri': + server_status[key] = value.replace(self._config['wss-host'], public_host) + return server_status + return WebhookRequestHandler |