summaryrefslogtreecommitdiffstats
path: root/gitautodeploy/models
diff options
context:
space:
mode:
authorOliver Poignant <oliver@poignant.se>2017-01-08 17:15:58 +0100
committerOliver Poignant <oliver@poignant.se>2017-01-08 17:15:58 +0100
commit465cb9263fb1ffc9ba2d4a912847322920180caa (patch)
tree59932075b5f6426088704abac7c53f8c476da2a4 /gitautodeploy/models
parent324c3518d4b98da5bb660c62fa1503c47139c088 (diff)
downloadGit-Auto-Deploy-master.zip
Git-Auto-Deploy-master.tar.gz
Git-Auto-Deploy-master.tar.bz2
Diffstat (limited to 'gitautodeploy/models')
-rw-r--r--gitautodeploy/models/__init__.py1
-rw-r--r--gitautodeploy/models/project.py210
2 files changed, 211 insertions, 0 deletions
diff --git a/gitautodeploy/models/__init__.py b/gitautodeploy/models/__init__.py
new file mode 100644
index 0000000..e2ebdf8
--- /dev/null
+++ b/gitautodeploy/models/__init__.py
@@ -0,0 +1 @@
+from .project import * \ No newline at end of file
diff --git a/gitautodeploy/models/project.py b/gitautodeploy/models/project.py
new file mode 100644
index 0000000..954da99
--- /dev/null
+++ b/gitautodeploy/models/project.py
@@ -0,0 +1,210 @@
+import collections
+from ..wrappers import GitWrapper
+from ..lock import Lock
+from ..wrappers import GitWrapper
+from ..events import DeployEvent
+
+
+class Project(collections.MutableMapping):
+
+ """A dictionary that applies an arbitrary key-altering
+ function before accessing the keys"""
+
+ def __init__(self, *args, **kwargs):
+ self.store = dict()
+ self.update(dict(*args, **kwargs)) # use the free update to set keys
+
+ def __getitem__(self, key):
+ return self.store[self.__keytransform__(key)]
+
+ def __setitem__(self, key, value):
+ self.store[self.__keytransform__(key)] = value
+
+ def __delitem__(self, key):
+ del self.store[self.__keytransform__(key)]
+
+ def __iter__(self):
+ return iter(self.store)
+
+ def __len__(self):
+ return len(self.store)
+
+ def __keytransform__(self, key):
+ return key
+
+ def get_name(self):
+ return self['url'].split('/')[-1].split('.git')[0]
+
+ def passes_payload_filter(self, payload, action):
+
+ # At least one filter must match
+ for filter in self['payload-filter']:
+
+ # 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, request_headers):
+
+ # At least one filter must match
+ for key in self['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 self['header-filter'][key] is True:
+ continue
+
+ # Verify that the request has the required header value
+ if self['header-filter'][key] != request_headers[key.lower()]:
+ return False
+
+ # Filter does match, proceed
+ return True
+
+ def apply_filters(self, 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 json
+
+ payload = json.loads(request_body)
+
+ # Verify that all payload filters matches the request (if any payload filters are specified)
+ if 'payload-filter' in self and not self.passes_payload_filter(payload, action):
+
+ # Filter does not match, do not process this repo config
+ return False
+
+ # Verify that all header filters matches the request (if any header filters are specified)
+ if 'header-filter' in self and not self.passes_header_filter(request_headers):
+
+ # Filter does not match, do not process this repo config
+ return False
+
+ return True
+
+ def execute_webhook(self, event_store):
+ """Verify that the suggested repositories has matching settings and
+ issue git pull and/or deploy commands."""
+ import os
+ import time
+ import json
+
+ event = DeployEvent(self)
+ 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 self:
+ res = GitWrapper.deploy(self)
+ 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(self['path']):
+ event.log_error("The repository '%s' does not exist locally. Make sure it was pulled properly without errors by reviewing the log." % self['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(self['path'], os.W_OK):
+ event.log_error("The path '%s' is not writable. Make sure that GAD has write access to that path." % self['path'])
+ event.set_waiting(False)
+ event.set_success(False)
+ return
+
+ running_lock = Lock(os.path.join(self['path'], 'status_running'))
+ waiting_lock = Lock(os.path.join(self['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(self)
+
+ # Return code indicating success?
+ if res == 0:
+ break
+
+ n -= 1
+
+ if 0 < n:
+ res = GitWrapper.deploy(self)
+
+ #except Exception as e:
+ # logger.error('Error during \'pull\' or \'deploy\' operation on path: %s' % self['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()
+
+ event.log_info("Deploy commands were executed")
+ event.set_waiting(False)
+ event.set_success(True)
+