diff --git a/OAS3.yml b/OAS3.yml index 501f221..16c65a6 100644 --- a/OAS3.yml +++ b/OAS3.yml @@ -200,84 +200,6 @@ paths: '404': $ref: '#/components/responses/NotFound' - /document/{id}/{type}: - parameters: - - in: path - name: id - required: true - schema: - type: string - description: Entity ID - - in: path - name: type - required: true - schema: - type: string - enum: [application, sepa] - description: Type of document to upload - - in: header - name: Authentication - schema: - type: string - description: Authentication token - post: - summary: Upload a PDF document for a member - description: Note that the entry must be updated with the URI obtained from this call - tags: - - document - requestBody: - description: The document - content: - 'application/pdf': - schema: - type: string - format: binary - responses: - '201': - description: File has been stored ("created") locally, returns the URI for downloading the file - content: - text/plain: - schema: - type: string - format: uri - '303': - description: The file is already in storage, returns the URI for downloading the file - content: - text/plain: - schema: - type: string - format: uri - '401': - $ref: '#/components/responses/AuthenticationRequired' - '403': - $ref: '#/components/responses/NotAllowed' - '405': - $ref: '#/components/responses/InvalidInput' - '500': - $ref: '#/components/responses/InternalError' - get: - summary: Get a PDF document for a member - tags: - - document - responses: - '200': - description: Returns PDF data - content: - 'application/pdf': - schema: - type: string - format: binary - '404': - $ref: '#/components/responses/NotFound' - '401': - $ref: '#/components/responses/AuthenticationRequired' - '403': - $ref: '#/components/responses/NotAllowed' - '405': - $ref: '#/components/responses/InvalidInput' - '500': - $ref: '#/components/responses/InternalError' - components: schemas: health: @@ -314,3 +236,4 @@ components: schema: type: string example: error message + diff --git a/README.md b/README.md index 2baa98e..a6d6afb 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,4 @@ Query and manipulate the Netz39 entities database. The service is configured via the following environment variables: * `PORT`: Service port. defaults to 8080 * `AUTH`: Authentication tokens, defaults to None. Example Configuration : `AUTH={"token_1": "user_1", "token_2": "user_2"}` -* `GIT_ORIGIN`: URL for the origin Git repository, including the user name -* `GIT_PASSWORD`: The git password for the user encoded in the origin URL -* `GIT_PULL_INTV`: Time interval between automated pull operations (default: 30s) -* `GIT_WC_PATH`: Set a path for the working copy. Will create a temporary checkout if not provided. + diff --git a/app.py b/app.py index 691e2d4..3fa8648 100644 --- a/app.py +++ b/app.py @@ -12,7 +12,6 @@ import json import util from auth import AuthProvider -from gitmgr import GitManagerConfiguration, GitManager startup_timestamp = datetime.now() @@ -40,9 +39,8 @@ class AuthenticatedHandler(tornado.web.RequestHandler, metaclass=ABCMeta): class HealthHandler(tornado.web.RequestHandler, metaclass=ABCMeta): # noinspection PyAttributeOutsideInit - def initialize(self, sources=None): + def initialize(self): self.git_version = self._load_git_version() - self.sources = sources @staticmethod def _load_git_version(): @@ -74,12 +72,6 @@ class HealthHandler(tornado.web.RequestHandler, metaclass=ABCMeta): health['timestamp'] = isodate.datetime_isoformat(datetime.now()) health['uptime'] = isodate.duration_isoformat(datetime.now() - startup_timestamp) - if self.sources: - for s in self.sources: - h = s() - if h is not None: - health = {**health, **h} - self.set_header("Content-Type", "application/json") self.write(json.dumps(health, indent=4)) self.set_status(200) @@ -121,14 +113,13 @@ class SingleEntityHandler(AuthenticatedHandler, metaclass=ABCMeta): pass -def make_app(_auth_provider=None, gitmgr=None): +def make_app(auth_provider=None): version_path = r"/v[0-9]" return tornado.web.Application([ - (version_path + r"/health", HealthHandler, - {"sources": [lambda: {"git-head": gitmgr.head_sha}] if gitmgr else None}), + (version_path + r"/health", HealthHandler), (version_path + r"/oas3", Oas3Handler), - (version_path + r"/entities", AllEntitiesHandler, {"auth_provider": _auth_provider}), - (version_path + r"/entity/{.*}", SingleEntityHandler, {"auth_provider": _auth_provider}), + (version_path + r"/entities", AllEntitiesHandler, {"auth_provider": auth_provider}), + (version_path + r"/entity/{.*}", SingleEntityHandler, {"auth_provider": auth_provider}), ]) @@ -138,16 +129,10 @@ def main(): # Setup auth_provider = AuthProvider.from_environment() - gitcfg = GitManagerConfiguration.from_environment() - gitmgr = GitManager(configuration=gitcfg) - gitmgr.setup() - gitmgr.printout() - - util.run_tornado_server(make_app(auth_provider, gitmgr), + util.run_tornado_server(make_app(auth_provider), server_port=port) # Teardown - gitmgr.teardown() print("Server stopped") diff --git a/gitmgr.py b/gitmgr.py deleted file mode 100644 index 43506e2..0000000 --- a/gitmgr.py +++ /dev/null @@ -1,188 +0,0 @@ -import git -import os -import shutil -import tempfile -import time - -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) - pull_intv = load_env("GIT_PULL_INTV", None) - - return GitManagerConfiguration(origin=origin, - git_pw=git_pw, - wc_path=wc_path, - pull_intv=pull_intv) - - def __init__(self, origin, git_pw=None, wc_path=None, pull_intv=None): - if not origin: - raise ValueError("Git origin cannot be empty!") - - self._origin = origin - self._git_pw = git_pw - self._wc_path = wc_path - self._pull_intv = 30 if pull_intv is None else int(pull_intv) - - @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 - - @property - def pull_intv(self): - return self._pull_intv - - -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 - self._last_pull = 0 - - @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() - self.pull(force=True) - - 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.") - - @property - def head_sha(self): - return None if self.repo is None else self.repo.head.object.hexsha - - def pull(self, force=False): - """Pull from origin. - - Arguments: - `force` -- Do a pull even though the pull interval has not elapsed - - Returns: True if pull was executed - """ - - if not force and (time.time() - self._last_pull < self.configuration.pull_intv): - return False - - self._last_pull = time.time() - - old_head = self.head_sha - - # get the origin - # (We verified during initialization that this origin exists.) - origin = self.repo.remote('origin') - - origin.pull(rebase=True) - - return self.head_sha != old_head diff --git a/requirements.txt b/requirements.txt index b803341..757931f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ tornado==6.0.4 isodate==0.6.0 pytest==5.4.1 -GitPython==3.1.12 \ No newline at end of file