Compare commits
6 commits
be957b54b4
...
5c01294808
Author | SHA1 | Date | |
---|---|---|---|
5c01294808 | |||
5f57bb4689 | |||
96870c2a15 | |||
b101698547 | |||
44770f00c3 | |||
aeb89fd77f |
10 changed files with 0 additions and 264 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -1,7 +0,0 @@
|
||||||
.env
|
|
||||||
|
|
||||||
venv/
|
|
||||||
.idea
|
|
||||||
|
|
||||||
__pycache__
|
|
||||||
.pytest_cache
|
|
26
Dockerfile
26
Dockerfile
|
@ -1,26 +0,0 @@
|
||||||
FROM alpine/git AS install
|
|
||||||
|
|
||||||
RUN echo "unknow" > /git-version.txt
|
|
||||||
|
|
||||||
# If the --dirty flag is left out, only the .git directory has to be copied
|
|
||||||
COPY . /git/
|
|
||||||
|
|
||||||
RUN find . -type d -name .git -exec git describe --always --dirty > /git-version.txt \;
|
|
||||||
|
|
||||||
|
|
||||||
FROM python:3.8
|
|
||||||
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
COPY test.sh /
|
|
||||||
|
|
||||||
COPY OAS3.yml /
|
|
||||||
|
|
||||||
COPY requirements.txt /
|
|
||||||
RUN pip install -r requirements.txt
|
|
||||||
|
|
||||||
COPY *.py /
|
|
||||||
|
|
||||||
COPY --from=install /git-version.txt /
|
|
||||||
|
|
||||||
CMD ["python", "-u", "./app.py"]
|
|
|
@ -2,9 +2,3 @@
|
||||||
|
|
||||||
Validate Netz39 entities JSON.
|
Validate Netz39 entities JSON.
|
||||||
|
|
||||||
## Running the Service
|
|
||||||
|
|
||||||
### Configuration
|
|
||||||
|
|
||||||
The service is configured via the following environment variables:
|
|
||||||
* `PORT`: Service port. defaults to 8080
|
|
||||||
|
|
93
app.py
93
app.py
|
@ -1,93 +0,0 @@
|
||||||
#!/usr/bin/python3
|
|
||||||
from abc import ABCMeta
|
|
||||||
|
|
||||||
import tornado.web
|
|
||||||
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
from datetime import datetime
|
|
||||||
import isodate
|
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
import util
|
|
||||||
|
|
||||||
|
|
||||||
startup_timestamp = datetime.now()
|
|
||||||
|
|
||||||
|
|
||||||
class HealthHandler(tornado.web.RequestHandler, metaclass=ABCMeta):
|
|
||||||
# noinspection PyAttributeOutsideInit
|
|
||||||
def initialize(self):
|
|
||||||
self.git_version = self._load_git_version()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _load_git_version():
|
|
||||||
v = None
|
|
||||||
|
|
||||||
# try file git-version.txt first
|
|
||||||
gitversion_file = "git-version.txt"
|
|
||||||
if os.path.exists(gitversion_file):
|
|
||||||
with open(gitversion_file) as f:
|
|
||||||
v = f.readline().strip()
|
|
||||||
|
|
||||||
# if not available, try git
|
|
||||||
if v is None:
|
|
||||||
try:
|
|
||||||
v = subprocess.check_output(["git", "describe", "--always", "--dirty"],
|
|
||||||
cwd=os.path.dirname(__file__)).strip().decode()
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
print("Checking git version lead to non-null return code ", e.returncode)
|
|
||||||
|
|
||||||
return v
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
health = dict()
|
|
||||||
health['api-version'] = 'v0'
|
|
||||||
|
|
||||||
if self.git_version is not None:
|
|
||||||
health['git-version'] = self.git_version
|
|
||||||
|
|
||||||
health['timestamp'] = isodate.datetime_isoformat(datetime.now())
|
|
||||||
health['uptime'] = isodate.duration_isoformat(datetime.now() - startup_timestamp)
|
|
||||||
|
|
||||||
self.set_header("Content-Type", "application/json")
|
|
||||||
self.write(json.dumps(health, indent=4))
|
|
||||||
self.set_status(200)
|
|
||||||
|
|
||||||
|
|
||||||
class Oas3Handler(tornado.web.RequestHandler, metaclass=ABCMeta):
|
|
||||||
def get(self):
|
|
||||||
self.set_header("Content-Type", "text/plain")
|
|
||||||
# This is the proposed content type,
|
|
||||||
# but browsers like Firefox try to download instead of display the content
|
|
||||||
# self.set_header("Content-Type", "text/vnd.yml")
|
|
||||||
with open('OAS3.yml', 'r') as f:
|
|
||||||
oas3 = f.read()
|
|
||||||
self.write(oas3)
|
|
||||||
self.finish()
|
|
||||||
|
|
||||||
|
|
||||||
def make_app(_auth_provider=None):
|
|
||||||
version_path = r"/v[0-9]"
|
|
||||||
return tornado.web.Application([
|
|
||||||
(version_path + r"/health", HealthHandler),
|
|
||||||
(version_path + r"/oas3", Oas3Handler),
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
port = util.load_env('PORT', 8080)
|
|
||||||
|
|
||||||
# Setup
|
|
||||||
|
|
||||||
util.run_tornado_server(make_app(auth_provider),
|
|
||||||
server_port=port)
|
|
||||||
|
|
||||||
# Teardown
|
|
||||||
|
|
||||||
print("Server stopped")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
|
@ -1,16 +0,0 @@
|
||||||
# Do not forget to create the .env file (see template)
|
|
||||||
# before using this container!
|
|
||||||
|
|
||||||
version: '2'
|
|
||||||
|
|
||||||
services:
|
|
||||||
entities_validation_service:
|
|
||||||
restart: always
|
|
||||||
build: .
|
|
||||||
env_file:
|
|
||||||
- .env
|
|
||||||
environment:
|
|
||||||
PORT: 8080
|
|
||||||
ports:
|
|
||||||
- $PORT:8080
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
PORT=8080
|
|
|
@ -1,4 +1 @@
|
||||||
tornado==6.0.4
|
|
||||||
isodate==0.6.0
|
|
||||||
pytest==5.4.1
|
|
||||||
schwifty==2020.9.0
|
schwifty==2020.9.0
|
||||||
|
|
42
test.py
42
test.py
|
@ -1,42 +0,0 @@
|
||||||
#!/usr/bin/python3
|
|
||||||
|
|
||||||
from app import make_app
|
|
||||||
import util
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
import tornado.testing
|
|
||||||
import json
|
|
||||||
|
|
||||||
util.platform_setup()
|
|
||||||
|
|
||||||
|
|
||||||
class TestBaseAPI(tornado.testing.AsyncHTTPTestCase):
|
|
||||||
"""Example test case"""
|
|
||||||
def get_app(self):
|
|
||||||
return make_app()
|
|
||||||
|
|
||||||
def test_health_endpoint(self):
|
|
||||||
response = self.fetch('/v0/health',
|
|
||||||
method='GET')
|
|
||||||
self.assertEqual(200, response.code, "GET /health must be available")
|
|
||||||
|
|
||||||
health = json.loads(response.body.decode())
|
|
||||||
|
|
||||||
self.assertIn('api-version', health, msg="api-version is not provided by health endpoint")
|
|
||||||
self.assertEqual("v0", health['api-version'], msg="API version should be v0")
|
|
||||||
self.assertIn('git-version', health, msg="git-version is not provided by health endpoint")
|
|
||||||
self.assertIn('timestamp', health, msg="timestamp is not provided by health endpoint")
|
|
||||||
self.assertIn('uptime', health, msg="uptime is not provided by health endpoint")
|
|
||||||
|
|
||||||
def test_oas3(self):
|
|
||||||
response = self.fetch('/v0/oas3',
|
|
||||||
method='GET')
|
|
||||||
self.assertEqual(200, response.code, "GET /oas3 must be available")
|
|
||||||
|
|
||||||
# check contents against local OAS3.yml
|
|
||||||
with open('OAS3.yml') as oas3f:
|
|
||||||
self.assertEqual(response.body.decode(), oas3f.read(), "OAS3 content differs from spec file!")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
unittest.main()
|
|
3
test.sh
3
test.sh
|
@ -1,3 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
python3 -m pytest test.py
|
|
67
util.py
67
util.py
|
@ -1,67 +0,0 @@
|
||||||
import os
|
|
||||||
import signal
|
|
||||||
import platform
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
import tornado.ioloop
|
|
||||||
import tornado.netutil
|
|
||||||
import tornado.httpserver
|
|
||||||
|
|
||||||
|
|
||||||
def load_env(key, default):
|
|
||||||
if key in os.environ:
|
|
||||||
return os.environ[key]
|
|
||||||
else:
|
|
||||||
return default
|
|
||||||
|
|
||||||
|
|
||||||
signal_received = False
|
|
||||||
|
|
||||||
|
|
||||||
def platform_setup():
|
|
||||||
"""Platform-specific setup, especially for asyncio."""
|
|
||||||
if platform.system() == 'Windows':
|
|
||||||
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
||||||
|
|
||||||
|
|
||||||
def run_tornado_server(app, server_port=8080):
|
|
||||||
platform_setup()
|
|
||||||
|
|
||||||
sockets = tornado.netutil.bind_sockets(server_port, '')
|
|
||||||
server = tornado.httpserver.HTTPServer(app)
|
|
||||||
server.add_sockets(sockets)
|
|
||||||
|
|
||||||
port = None
|
|
||||||
|
|
||||||
for s in sockets:
|
|
||||||
print('Listening on %s, port %d' % s.getsockname()[:2])
|
|
||||||
if port is None:
|
|
||||||
port = s.getsockname()[1]
|
|
||||||
|
|
||||||
ioloop = tornado.ioloop.IOLoop.instance()
|
|
||||||
|
|
||||||
def register_signal(sig, _frame):
|
|
||||||
# noinspection PyGlobalUndefined
|
|
||||||
global signal_received
|
|
||||||
print("%s received, stopping server" % sig)
|
|
||||||
server.stop() # no more requests are accepted
|
|
||||||
signal_received = True
|
|
||||||
|
|
||||||
def stop_on_signal():
|
|
||||||
# noinspection PyGlobalUndefined
|
|
||||||
global signal_received
|
|
||||||
if signal_received:
|
|
||||||
ioloop.stop()
|
|
||||||
print("IOLoop stopped")
|
|
||||||
|
|
||||||
tornado.ioloop.PeriodicCallback(stop_on_signal, 1000).start()
|
|
||||||
signal.signal(signal.SIGTERM, register_signal)
|
|
||||||
print("Starting server")
|
|
||||||
|
|
||||||
global signal_received
|
|
||||||
while not signal_received:
|
|
||||||
try:
|
|
||||||
ioloop.start()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Keyboard interrupt")
|
|
||||||
register_signal(signal.SIGTERM, None)
|
|
Loading…
Add table
Reference in a new issue