From 234421fb6467055c9fce9c2a0806aae1bec8144b Mon Sep 17 00:00:00 2001 From: Stefan Haun Date: Thu, 11 Feb 2021 20:58:42 +0100 Subject: [PATCH] Add the GitManager with configuration and repository setup --- gitmgr.py | 150 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 gitmgr.py diff --git a/gitmgr.py b/gitmgr.py new file mode 100644 index 0000000..eef02d7 --- /dev/null +++ b/gitmgr.py @@ -0,0 +1,150 @@ +import git +import os +import shutil +import tempfile + +from util import load_env + + +class GitManagerConfiguration: + @staticmethod + def from_environment(): + origin = load_env("GIT_ORIGIN", None) + wc_path = load_env("GIT_WC_PATH", None) + git_pw = load_env("GIT_PASSWORD", None) + + return GitManagerConfiguration(origin=origin, + git_pw=git_pw, + wc_path=wc_path) + + def __init__(self, origin, git_pw=None, wc_path=None): + if not origin: + raise ValueError("Git origin cannot be empty!") + + self._origin = origin + self._git_pw = git_pw + self._wc_path = wc_path + + @property + def origin(self): + return self._origin + + @property + def git_pw(self): + return self._git_pw + + @property + def wc_path(self): + return self._wc_path + + +class GitManager: + def __init__(self, configuration): + if configuration is None: + raise ValueError("GitManager must be initialized with a configuration!") + + self._configuration = configuration + self._wc = None + + @property + def configuration(self): + return self._configuration + + def _setup_wc(self): + if self._wc is not None: + return + + _wc = self.configuration.wc_path + + if _wc is None: + _wc = tempfile.mkdtemp(prefix='entities_git_') + + if not os.path.isdir(_wc): + raise ValueError("Configured directory for the working copy does not exist!") + + self._wc = _wc + + def _teardown_wc(self): + if self._wc is None: + return + + if self.configuration.wc_path is not None: + print("NOTE: Not tearing down externally configured working copy.") + return + + shutil.rmtree(self._wc) + + self._wc = None + + def _assert_wc(self): + """Assert working copy matches origin and is a valid repository. + + A failed assertion will throw exceptions and lead to service abort, + as this error is not recoverable. + + Returns False if the WC path is an empty directory""" + + # Check if WC is empty + if not os.listdir(self._wc): + return False + + # Create a repository object + # This fails if there is no valid repository + repo = git.Repo(self._wc) + + # Assert that this is not a bare repo + if repo.bare: + raise ValueError("WC path points to a bare git repository!") + + origin = repo.remote('origin') + if self.configuration.origin not in origin.urls: + raise ValueError("Origin URL does not match!") + + # We're good here. + return True + + def _askpass_script(self): + # Passwords are impossible to store in scripts, as they may contain any character ... + # We convert the password into a list of integers and create a little script + # that reconstructs the password and writes it to the console. + # Python will be installed anyways. + + pw_chars = [ord(c) for c in self.configuration.git_pw] + + script = "#!/usr/bin/env python3\n" + script += "l = %s\n" % str(list(pw_chars)) + script += "p = [chr(c) for c in l]\n" + script += f"print(\"\".join(p))\n" + return script + + def _init_repo(self): + # Assert working copy is valid, + # return false if cloning is necessary + if not self._assert_wc(): + print("Cloning new git working copy ...") + + # Create a temporary script file for GIT_ASKPASS + with tempfile.NamedTemporaryFile(mode='w+t') as askpass: + askpass.write(self._askpass_script()) + askpass.file.close() + os.chmod(path=askpass.name, mode=0o700) + self.repo = git.Repo.clone_from(url=self.configuration.origin, + to_path=self._wc, + env={'GIT_ASKPASS': askpass.name}) + else: + print("Reusing existing git working copy ...") + self.repo = git.Repo(self._wc) + + def setup(self): + self._setup_wc() + self._init_repo() + + def teardown(self): + self._teardown_wc() + + def printout(self): + print("Git Manager:") + print(f"\tGit origin is %s" % self.configuration.origin) + print(f"\tUsing working copy path %s" % self._wc) + if not self._wc == self.configuration.wc_path: + print("\tUsing a temporary working copy.")