diff options
Diffstat (limited to 'gitautodeploy')
-rw-r--r-- | gitautodeploy/cli/config.py | 9 | ||||
-rw-r--r-- | gitautodeploy/events.py | 18 | ||||
-rw-r--r-- | gitautodeploy/gitautodeploy.py | 6 | ||||
-rw-r--r-- | gitautodeploy/httpserver.py | 46 | ||||
-rw-r--r-- | gitautodeploy/webhook.py | 162 |
5 files changed, 140 insertions, 101 deletions
diff --git a/gitautodeploy/cli/config.py b/gitautodeploy/cli/config.py index 2d2e301..3af4e3d 100644 --- a/gitautodeploy/cli/config.py +++ b/gitautodeploy/cli/config.py @@ -44,6 +44,7 @@ def get_config_defaults(): config['web-ui-whitelist'] = ['127.0.0.1'] config['web-ui-require-https'] = True config['web-ui-auth-enabled'] = True + config['web-ui-prevent-root'] = True # Record all log levels by default config['log-level'] = 'NOTSET' @@ -51,6 +52,7 @@ def get_config_defaults(): # Other options config['intercept-stdout'] = True config['ssh-keyscan'] = False + config['allow-root-user'] = False # Include details with deploy command return codes in HTTP response. Causes # to await any git pull or deploy command actions before it sends the @@ -232,6 +234,13 @@ def get_config_from_argv(argv): dest="ssl-cert", type=str) + parser.add_argument("--allow-root-user", + help="allow running as root user", + dest="allow-root-user", + default=None, + action="store_true") + + config = vars(parser.parse_args(argv)) # Delete entries for unprovided arguments diff --git a/gitautodeploy/events.py b/gitautodeploy/events.py index 3a86700..5a4cef5 100644 --- a/gitautodeploy/events.py +++ b/gitautodeploy/events.py @@ -95,6 +95,24 @@ class WebhookAction(SystemEvent): return data +class DeployEvent(SystemEvent): + + def __init__(self, repo_config): + self.repo_config = repo_config + super(DeployEvent, self).__init__() + + def __repr__(self): + return "<WebhookAction>" + + def dict_repr(self): + data = super(DeployEvent, self).dict_repr() + data['name'] = self.get_name() + return data + + def get_name(self): + return self.repo_config['url'].split('/')[-1].split('.git')[0] + + class StartupEvent(SystemEvent): def __init__(self, http_address=None, http_port=None, ws_address=None, ws_port=None): diff --git a/gitautodeploy/gitautodeploy.py b/gitautodeploy/gitautodeploy.py index 6bf8693..31e1696 100644 --- a/gitautodeploy/gitautodeploy.py +++ b/gitautodeploy/gitautodeploy.py @@ -172,6 +172,7 @@ class GitAutoDeploy(object): import logging import base64 from .lock import Lock + import getpass # This solves https://github.com/olipo186/Git-Auto-Deploy/issues/118 try: @@ -218,6 +219,11 @@ class GitAutoDeploy(object): fileHandler.setFormatter(logFormatter) logger.addHandler(fileHandler) + # Display a warning when trying to run as root + if not self._config['allow-root-user'] and getpass.getuser() == 'root': + logger.critical("Refusing to start. This application shouldn't run as root. Please run it as a different user. To disregard this warning and start anyway, set the config option \"allow-root-user\" to true, or use the command line argument --allow-root-user") + sys.exit() + if 'ssh-keyscan' in self._config and self._config['ssh-keyscan']: self._startup_event.log_info('Scanning repository hosts for ssh keys...') self.ssh_key_scan() diff --git a/gitautodeploy/httpserver.py b/gitautodeploy/httpserver.py index 65ccc3d..857f5f3 100644 --- a/gitautodeploy/httpserver.py +++ b/gitautodeploy/httpserver.py @@ -104,7 +104,7 @@ def WebhookRequestHandlerFactory(config, event_store, server_status, is_https=Fa request_headers = dict((k.lower(), v) for k, v in request_headers.items()) action = WebhookAction(self.client_address, request_headers, request_body) - event_store.register_action(action) + self._event_store.register_action(action) action.set_waiting(True) action.log_info('Incoming request from %s:%s' % (self.client_address[0], self.client_address[1])) @@ -129,6 +129,8 @@ def WebhookRequestHandlerFactory(config, event_store, server_status, is_https=Fa self.send_error(400, 'Unrecognized service') test_case['expected']['status'] = 400 action.log_error("Unable to find appropriate handler for request. The source service is not supported") + action.set_waiting(False) + action.set_success(False) return service_handler = ServiceRequestHandler(self._config) @@ -138,21 +140,29 @@ def WebhookRequestHandlerFactory(config, event_store, server_status, is_https=Fa # Could be GitHubParser, GitLabParser or other repo_configs = service_handler.get_repo_configs(request_headers, request_body, action) + action.log_info("%s candidates found" % len(repo_configs)) + request_filter = WebhookRequestFilter() if len(repo_configs) == 0: self.send_error(400, 'Bad request') test_case['expected']['status'] = 400 action.log_error("No matching repository config") + action.set_waiting(False) + action.set_success(False) return # Apply filters repo_configs = request_filter.apply_filters(repo_configs, request_headers, request_body, action) + action.log_info("%s candidates matches the filters" % len(repo_configs)) + if not service_handler.validate_request(request_headers, repo_configs, action): self.send_error(400, 'Bad request') test_case['expected']['status'] = 400 - action.log_warning("Request not valid") + action.log_warning("Request is not valid") + action.set_waiting(False) + action.set_success(False) return test_case['expected']['status'] = 200 @@ -162,22 +172,27 @@ def WebhookRequestHandlerFactory(config, event_store, server_status, is_https=Fa self.end_headers() if len(repo_configs) == 0: - action.log_info("Filter does not match") + action.set_waiting(False) + action.set_success(False) return - action.log_info("Executing deploy commands") + action.log_info("Proceeding with %s candidates" % len(repo_configs)) + action.set_waiting(False) + action.set_success(True) + + for repo_config in repo_configs: - # Schedule the execution of the webhook (git pull and trigger deploy etc) - thread = threading.Thread(target=request_processor.execute_webhook, args=[repo_configs, request_headers, request_body, action]) - thread.start() + # Schedule the execution of the webhook (git pull and trigger deploy etc) + thread = threading.Thread(target=request_processor.execute_webhook, args=[repo_config, self._event_store]) + thread.start() - # Add additional test case data - test_case['config'] = { - 'url': 'url' in repo_configs[0] and repo_configs[0]['url'], - 'branch': 'branch' in repo_configs[0] and repo_configs[0]['branch'], - 'remote': 'remote' in repo_configs[0] and repo_configs[0]['remote'], - 'deploy': 'echo test!' - } + # Add additional test case data + test_case['config'] = { + 'url': 'url' in repo_config and repo_config['url'], + 'branch': 'branch' in repo_config and repo_config['branch'], + 'remote': 'remote' in repo_config and repo_config['remote'], + 'deploy': 'echo test!' + } except ValueError as e: self.send_error(400, 'Unprocessable request') @@ -210,8 +225,7 @@ def WebhookRequestHandlerFactory(config, event_store, server_status, is_https=Fa go through our custom logger instead.""" import logging logger = logging.getLogger() - logger.info("%s - %s" % (self.client_address[0], - format%args)) + logger.info("%s - %s" % (self.client_address[0], format%args)) def save_test_case(self, test_case): """Log request information in a way it can be used as a test case.""" diff --git a/gitautodeploy/webhook.py b/gitautodeploy/webhook.py index 5c027e4..752717e 100644 --- a/gitautodeploy/webhook.py +++ b/gitautodeploy/webhook.py @@ -51,105 +51,97 @@ class WebbhookRequestProcessor(object): 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): + def execute_webhook(self, repo_config, event_store): """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 .events import DeployEvent 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: + event = DeployEvent(repo_config) + event_store.register_action(event) + event.set_waiting(True) + event.log_info("Running deploy commands") + + # 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) + event.log_info("%s" % res) + event.set_waiting(False) + event.set_success(True) + return + + # 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']): + event.log_error("The repository '%s' does not exist locally. Make sure it was pulled properly without errors by reviewing the log." % repo_config['path']) + event.set_waiting(False) + event.set_success(False) + return + + # 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): + event.log_error("The path '%s' is not writable. Make sure that GAD has write access to that path." % repo_config['path']) + event.set_waiting(False) + event.set_success(False) + return + + 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(): + event.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) + + # Return code indicating success? + if res == 0: + break + + n -= 1 + + if 0 < n: 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() + #except Exception as e: + # logger.error('Error during \'pull\' or \'deploy\' operation on path: %s' % repo_config['path']) + # logger.error(e) + # raise e - # Release the lock if it's ours - if waiting_lock.has_lock(): - waiting_lock.release() + finally: - result.append(repo_result) + # Release the lock if it's ours + if running_lock.has_lock(): + running_lock.release() - action.log_info("Deploy commands were executed") - action.set_waiting(False) - action.set_success(True) + # Release the lock if it's ours + if waiting_lock.has_lock(): + waiting_lock.release() - return result + event.log_info("Deploy commands were executed") + event.set_waiting(False) + event.set_success(True) class WebhookRequestFilter(object): |