Commit 28584b2c authored by Konrad Mohrfeldt's avatar Konrad Mohrfeldt

initial release

parents
Pipeline #1969 failed with stages
in 5 minutes and 17 seconds
[bumpversion]
current_version = 0.1.0
[bumpversion:file:VERSION]
root = true
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
charset = utf-8
[{make.d/*,Makefile,debian/rules}]
indent_style = tab
__pycache__/
/build/
/.pybuild/
/dist/
/lohro_track_api.egg-info/
*~
debian/debhelper-build-stamp
debian/lohro-track-api/
debian/python3-lohro-track-api/
debian/*.debhelper
debian/*.substvars
debian/*.debhelper.log
debian/files
debian/tmp/
stages:
- test
- build
before_script:
- make ci_deps_debian
test_buster:
image: $CI_REGISTRY_IMAGE/build:buster
stage: test
script:
- make ci_settings test
make_deb_package:
stage: build
tags:
- fast-io
only:
- tags
- triggers
- schedules
- merge_requests
- web
script:
- make dist-deb DEBIAN_BUILDPACKAGE_COMMAND="dpkg-buildpackage --no-sign"
artifacts:
paths:
- build/debian
expire_in: 1w
include make.d/makefilet-download-ondemand.mk
.PHONY: default-target
default-target:
true
include make.d/ci.mk
# lohro-track-api
Small tornado service implementing the Thekno track service API.
# Run
Run with `python3 -m lohro_track_api` or `PYTHONPATH=. -m lohro_track_api` when
the library is not installed in your system. See `python3 -m lohro_track_api --help`
for available options.
lohro-track-api looks for config files in `/etc/lohro-track-api/config.conf`
and `~/.config/lohro-track-api.conf`.
lohro-track-api (0.1.0-1) unstable; urgency=medium
* New upstream release
-- Konrad Mohrfeldt <konrad.mohrfeldt@farbdev.org> Mon, 05 Oct 2019 19:47:12 +0200
Source: lohro-track-api
Section: web
Priority: optional
Maintainer: Konrad Mohrfeldt <konrad.mohrfeldt@farbdev.org>
Standards-Version: 4.4.0
Build-Depends:
debhelper (>= 9),
default-libmysqlclient-dev,
dh-exec,
python3,
python3-dev,
python3-flake8,
python3-pip,
python3-setuptools,
python3-venv,
Homepage: https://git.hack-hro.de/lohro/lohrothek/lohro-track-api
Package: python3-lohro-track-api
Architecture: all
Depends:
${python3:Depends},
${misc:Depends},
python3,
python3-tornado,
python3-mysqldb,
Description: A tornado web app implementing the Thekno track-service API
Package: lohro-track-api
Architecture: all
Depends:
${misc:Depends},
adduser,
python3-lohro-track-api,
Description: System configuration for the lohro-track-api tornado service
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Source: https://git.hack-hro.de/lohro/lohrothek/lohro-track-api
Files: *
Copyright: 2019 Konrad Mohrfeldt <konrad.mohrfeldt@farbdev.org>
License: AGPL-3+
system-files/nginx-defaultsite.conf
#!/usr/bin/dh-exec
system-files/nginx.conf => /etc/nginx/snippets/lohro-track-api.conf
#!/bin/sh
set -eu
DIR_HOME="/var/lib/lohro-track-api"
PKG_USER="lohro-track-api"
if [ "$1" = "configure" ]; then
if ! getent passwd "$PKG_USER" >/dev/null; then
adduser --quiet --system --group --disabled-password \
--home "$DIR_HOME" "$PKG_USER"
fi
fi
set +eu
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
#DEBHELPER#
exit 0
#!/bin/sh
set -eu
DIR_HOME="/var/lib/lohro-track-api"
PKG_USER="lohro-track-api"
if [ "$1" = "remove" ]; then
if getent passwd "$PKG_USER" >/dev/null; then
deluser --quiet "$PKG_USER"
fi
elif [ "$1" = "purge" ]; then
rm -rf "$DIR_HOME"
fi
set +eu
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
#DEBHELPER#
exit 0
[Unit]
Description=Thekno track-service API
After=network.target
After=mysqld.service
[Service]
ExecStart=/usr/bin/env python3 -m lohro_track_api
User=lohro-track-api
[Install]
WantedBy=multi-user.target
#!/usr/bin/make -f
#export DH_VERBOSE=1
%:
PYBUILD_NAME=lohro_track_api PYBUILD_DISABLE=test \
dh $@ --package=python3-lohro-track-api \
--with python3 --buildsystem pybuild --no-guessing-deps
dh $@ --package=lohro-track-api
import json
import logging
from tornado.options import options, define
import tornado.ioloop
import tornado.web
import tornado.websocket
from lohro_track_api.database import list_tracks, get_current_track
define('allowed_websocket_origins', type=str, multiple=True, default=None,
help='Check if a websocket originates from a valid source. '
'Per default all origins are allowed.')
CURRENT_TRACK = None
def _update_current_track():
global CURRENT_TRACK
current_track = get_current_track()
if CURRENT_TRACK != current_track:
CURRENT_TRACK = current_track
EventBroadcaster.signal_clients({
'source': 'current_track',
'event': 'update',
'payload': current_track.serialize() if CURRENT_TRACK is not None else None
})
class EventBroadcaster(tornado.websocket.WebSocketHandler):
CLIENTS = set()
LAST_EVENT = None
def check_origin(self, origin):
if not options.allowed_websocket_origins:
return True
else:
return origin in options.allowed_websocket_origins
def open(self):
self.CLIENTS.add(self)
if self.LAST_EVENT:
self.send_event(self.LAST_EVENT)
def on_close(self):
self.CLIENTS.remove(self)
def send_event(self, payload):
try:
self.write_message(payload)
except tornado.websocket.WebSocketClosedError:
EventBroadcaster.CLIENTS.remove(self)
except tornado.websocket.WebSocketError as exc:
logging.error('could not send track update to client', exc_info=exc)
@classmethod
def signal_clients(cls, payload):
event_payload = json.dumps(payload)
cls.LAST_EVENT = event_payload
for client in cls.CLIENTS:
client.send_event(event_payload)
class PaginatedHandler(tornado.web.RequestHandler):
@property
def page_args(self):
try:
page = max(1, int(self.get_argument('page', None)))
except (TypeError, ValueError):
page = 1
try:
page_size = max(1, min(100, int(self.get_argument('page_size', None))))
except (TypeError, ValueError):
page_size = 100
return dict(page=page, page_size=page_size)
class TrackCollectionHandler(PaginatedHandler):
def get(self):
tracks = list_tracks(**self.page_args)
self.write(json.dumps({
'results': [track.serialize() for track in tracks]
}))
def make_app():
tornado.ioloop.PeriodicCallback(_update_current_track, 3000).start()
return tornado.web.Application([
(r'/api/tracks', TrackCollectionHandler),
(r'/api/events', EventBroadcaster)
])
import os
import tornado.ioloop
from tornado.options import define, options, parse_command_line, parse_config_file
from lohro_track_api import make_app
define('port', default=8888, type=int,
help='Network port to run the service on')
def _get_user_config_dir():
return os.environ.get('XDG_CONFIG_HOME',
os.path.join(os.path.expanduser('~'), '.config'))
def _load_config():
configs = [
'/etc/lohro-track-api/config.conf',
os.path.join(_get_user_config_dir(), 'lohro-track-api.conf')
]
for config_path in configs:
try:
parse_config_file(config_path, final=False)
except FileNotFoundError:
pass
if __name__ == '__main__':
_load_config()
parse_command_line()
app = make_app()
app.listen(options.port, address='127.0.0.1')
tornado.ioloop.IOLoop.current().start()
import contextlib
import MySQLdb
from tornado.options import options, define
from lohro_track_api.utils import parse_database_url
from lohro_track_api.models import Track
define('database_url', default='mysql://localhost/lohro-wp-prod', type=str,
help='the database to retrieve data from')
@contextlib.contextmanager
def _database():
db = MySQLdb.connect(**parse_database_url(options.database_url))
try:
yield db.cursor()
finally:
db.close()
def _query_collection(table, hydrator, page=1, page_size=100, where=None, order=None):
offset = (page - 1) * page_size
with _database() as cursor:
cursor.execute(
'SELECT * FROM {table} {where} {order} '
'LIMIT %s OFFSET %s'.format(table=table,
where='' if where is None else where,
order='' if order is None else order),
(page_size, offset)
)
return [hydrator(row) for row in cursor.fetchall()]
def list_tracks(*args, **kwargs):
return _query_collection(
'lohro_trackservice', Track.hydrate_from_db,
order='ORDER BY start DESC', **kwargs)
def get_current_track():
return list_tracks(page=1, page_size=1)[0]
import datetime
class Track:
def __init__(self, pk, title, description, started_at):
self.id = pk
self.title = title
self.description = description
self.started_at = started_at
def __eq__(self, other):
return isinstance(other, Track) and other.id == self.id
def serialize(self):
return dict(
id=self.id,
title=self.title,
description=self.description,
started_at=self.started_at.isoformat(),
finished_at=None,
cover_image=None,
)
@classmethod
def hydrate_from_db(cls, row):
pk, start, artist, title = row
return cls(pk, title, artist,
datetime.datetime.fromtimestamp(start))
from urllib.parse import urlparse
def parse_database_url(url):
url_obj = urlparse(url)
return {
'host': url_obj.hostname,
'port': url_obj.port,
'user': url_obj.username,
'passwd': url_obj.password,
'db': url_obj.path[1:]
}
DOCKER_DEBIAN_TARGETS = buster
DOCKER_TEMPLATE = make.d/res/Dockerfile
DOCKER_REGISTRY = git-registry.hack-hro.de/lohro/lohrothek/lohro-track-api
# Install project debian dependencies in a CI environment.
.PHONY: ci_deps_debian
ci_deps_debian:
# if you change any of these dependencies make sure to update the docker images
# with `make ci_docker_images`
apt install -qyy \
debhelper default-libmysqlclient-dev locales \
python3-all python3-dev python3-flake8 python3-pip python3-setuptools python3-venv
# Login into the registry in order to upload new docker images
.PHONY: ci_docker_login
ci_docker_login:
@echo "Login credentials correspond to those of your GitLab account"
docker login $(DOCKER_REGISTRY)
# Create docker for targeted deployment platforms that can be used in tests
# or packaging builds. This is the only make target (apart from ci_docker_login)
# meant to be executed by a user and not a CI runner.
.PHONY: ci_docker_images
.ONESHELL:
ci_docker_images:
set -eu
for target in $(DOCKER_DEBIAN_TARGETS); do \
build_dir="$$(mktemp -d)"; \
cp -r --target-directory="$$build_dir" Makefile make.d; \
cat $(DOCKER_TEMPLATE) | sed -e "s/__release__/$$target/g" > "$$build_dir/Dockerfile"; \
docker build --no-cache --pull -t "$(DOCKER_REGISTRY)/build:$$target" "$$build_dir"; \
rm -rf "$$build_dir"; \
done
@echo "You have to push the docker images to the registry with:"
@for target in $(DOCKER_DEBIAN_TARGETS); do \
echo " docker push '$(DOCKER_REGISTRY)/build:$$target'"; \
done
@echo "In case access is denied run 'make ci_docker_login' and try again."
# include this makefile snippet for automatic discovery of a:
# * system-wide 'makefilet' installation (e.g. via the deb package)
# * project-wide inclusion as a git-submodule (or code copy)
# * on-demand download (temporarily stored in the local 'build/' directory)
# alternative version: "master" (the tip of the current development branch)
MAKEFILET_DOWNLOAD_VERSION ?= v0.5.3
MAKEFILET_DOWNLOAD_URL ?= https://notabug.org/sumpfralle/makefilet/archive/$(MAKEFILET_DOWNLOAD_VERSION).tar.gz
# first attempt: system-wide installation (e.g. deb package) or submodule of this project?
-include makefilet/main.mk
ifeq ($(DIR_MAKEFILET), )
# we failed - it is not available globally or as a submodule
# second attempt: include a downloaded makefilet archive
DIR_BUILD ?= build
DIR_MAKEFILET_DOWNLOAD = $(DIR_BUILD)/makefilet
-include $(DIR_MAKEFILET_DOWNLOAD)/main.mk
ifeq ($(DIR_MAKEFILET), )
# third attempt: download and extract a known release
$(info Downloading 'makefilet' ...)
$(shell mkdir -p "$(DIR_MAKEFILET_DOWNLOAD)" \
&& wget -q -O - "$(MAKEFILET_DOWNLOAD_URL)" \
| tar -xz -C "$(DIR_MAKEFILET_DOWNLOAD)" --strip-components=1 -f -)
# last include attempt
-include $(DIR_MAKEFILET_DOWNLOAD)/main.mk
ifeq ($(DIR_MAKEFILET), )
$(error Failed to initialize 'makefilet'. It seems like it is neither installed system-wide, as a submodule or available via download.)
endif
endif
endif
FROM debian:__release__
ENV DEBIAN_FRONTEND noninteractive
COPY Makefile /make/
COPY make.d /make/make.d
RUN apt update -qyy && apt install -qyy make wget
RUN { cd /make; make ci_deps_debian; }
RUN printf "en_US.UTF-8 UTF-8\nde_DE.UTF-8 UTF-8\n" | tee /etc/locale.gen && locale-gen
ENV LANG de_DE.UTF-8
ENV LC_MESSAGES en_US.UTF-8
[flake8]
max-line-length = 99
exclude = build, debian
import os
from setuptools import setup, find_packages
def get_version():
with open(os.path.join(os.path.dirname(__file__), 'VERSION')) as f:
return f.read().strip()
setup(
name='lohro_track_api',
version=get_version(),
description='A tornado web app implementing the Thekno track-service API',
long_description=None,
url='https://git.hack-hro.de/lohro/lohrothek/lohro-track-api',
author='lohrothek Maintainers',
author_email='konrad.mohrfeldt@farbdev.org',
packages=find_packages(exclude=['build.*', 'debian.*']),
include_package_data=True,
classifiers=[
'Development Status :: 4 - Beta',
'License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)',
'Programming Language :: Python :: 3',
'Environment :: Web Environment',
'Topic :: Internet :: WWW/HTTP',
],
install_requires=(
'tornado<5',
'mysqlclient',
),
data_files=(
('.', ('VERSION', )),
),
)
upstream lohro-track-api {
server localhost:8888;
}
server {
listen 80;
server_name lohro-track-api.example.com;
include snippets/lohro-track-api.conf;
}
location /api/ {
proxy_pass http://lohro-track-api;
add_header Access-Control-Allow-Origin '*';
}
location /api/events {
proxy_pass http://lohro-track-api;
add_header Access-Control-Allow-Origin '*';
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_read_timeout 1d;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment