Introduce configuration in server app.
This commit is contained in:
parent
50f0b64182
commit
7d6b445678
|
@ -1,5 +1,7 @@
|
||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
|
from distutils.util import strtobool
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# TODO: create config module and put this constant there.
|
# TODO: create config module and put this constant there.
|
||||||
BASE_URI = os.environ.get("BOTZ_BASE_URI", "http://localhost")
|
BASE_URI = os.environ.get("BOTZ_BASE_URI", "http://localhost")
|
||||||
|
DEBUG = strtobool(os.environ.get("BOTZ_DEBUG", "False"))
|
||||||
|
|
72
api/app.py
72
api/app.py
|
@ -8,6 +8,7 @@ from aiohttp import web
|
||||||
import base64
|
import base64
|
||||||
from cryptography import fernet
|
from cryptography import fernet
|
||||||
import logging
|
import logging
|
||||||
|
import logging.handlers
|
||||||
import os
|
import os
|
||||||
import typing as T
|
import typing as T
|
||||||
|
|
||||||
|
@ -17,15 +18,19 @@ from aiohttp_session.cookie_storage import EncryptedCookieStorage
|
||||||
|
|
||||||
from bot_z.async_operator import AsyncOperator
|
from bot_z.async_operator import AsyncOperator
|
||||||
from api.rest import routes
|
from api.rest import routes
|
||||||
|
from api.conf import read_conf
|
||||||
|
|
||||||
|
|
||||||
def setup_log() -> logging.Logger:
|
def setup_log(level: str, syslog: bool) -> logging.Logger:
|
||||||
alog = logging.getLogger("api")
|
alog = logging.getLogger("api")
|
||||||
alog.setLevel(os.environ.get("BOTZ_LOGLEVEL", logging.INFO))
|
alog.setLevel(os.environ.get("BOTZ_LOGLEVEL", level))
|
||||||
h = logging.StreamHandler()
|
h = logging.StreamHandler()
|
||||||
f = logging.Formatter("%(levelname)s [%(name)s] -> %(message)s")
|
f = logging.Formatter("%(levelname)s [%(name)s] -> %(message)s")
|
||||||
h.setFormatter(f)
|
h.setFormatter(f)
|
||||||
alog.addHandler(h)
|
alog.addHandler(h)
|
||||||
|
if syslog:
|
||||||
|
sh = logging.handlers.SysLogHandler()
|
||||||
|
alog.addHandler(sh)
|
||||||
return alog
|
return alog
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,28 +39,63 @@ def init_secret() -> bytes:
|
||||||
return base64.urlsafe_b64decode(fernet_key)
|
return base64.urlsafe_b64decode(fernet_key)
|
||||||
|
|
||||||
|
|
||||||
def setup_session(app: web.Application):
|
def setup_session(app: web.Application, secure: bool):
|
||||||
secret = init_secret()
|
secret = init_secret()
|
||||||
setup(app, EncryptedCookieStorage(secret))
|
setup(
|
||||||
|
app,
|
||||||
|
EncryptedCookieStorage(
|
||||||
|
secret_key=secret, cookie_name="BOTZ_SESSION", httponly=False, secure=secure
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def run(address: T.Text, port: int) -> None:
|
def run(
|
||||||
|
address: T.Optional[T.Text], port: T.Optional[int], conf_path: T.Optional[T.Text]
|
||||||
|
) -> None:
|
||||||
"""Application entrypoint."""
|
"""Application entrypoint."""
|
||||||
alog = setup_log()
|
conf = read_conf(conf_path)
|
||||||
|
|
||||||
|
# This closure is needed to intercept the to-be-prepared response
|
||||||
|
# and add the right CORS headers
|
||||||
|
async def on_prepare_cors(request, response):
|
||||||
|
response.headers["Access-Control-Allow-Origin"] = conf["http"].get("cors_allow")
|
||||||
|
response.headers["Access-Control-Allow-Credentials"] = "true"
|
||||||
|
|
||||||
|
alog = setup_log(conf["log"]["level"], conf["log"]["syslog"])
|
||||||
|
alog.debug("conf %s", conf)
|
||||||
app = web.Application(logger=alog)
|
app = web.Application(logger=alog)
|
||||||
setup_session(app)
|
app["base_uri"] = conf["base_uri"]
|
||||||
|
app["debug"] = conf["debug"]
|
||||||
|
app["headless"] = conf["headless"]
|
||||||
|
if conf["http"].get("cors_allow"):
|
||||||
|
app.on_response_prepare.append(on_prepare_cors)
|
||||||
|
setup_session(app, conf["http"]["cookie_secure"])
|
||||||
app.add_routes(routes)
|
app.add_routes(routes)
|
||||||
web.run_app(app, host=address, port=port)
|
addr = []
|
||||||
|
if address is not None:
|
||||||
|
addr.append(address)
|
||||||
|
if conf["http"].get("bind_addr"):
|
||||||
|
addr.extend(conf["http"]["bind_addr"])
|
||||||
|
if port is None:
|
||||||
|
port = conf["http"]["port"]
|
||||||
|
alog.debug("Starting app with: addr -> %s, port -> %d", addr, port)
|
||||||
|
web.run_app(app, host=addr, port=port)
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option(
|
@click.option(
|
||||||
"-a",
|
"-a", "--address", type=click.STRING, help="Address to bind the server to."
|
||||||
"--address",
|
|
||||||
type=click.STRING,
|
|
||||||
help="Address to bind the server to.",
|
|
||||||
default="127.0.0.1",
|
|
||||||
)
|
)
|
||||||
@click.option("-p", "--port", type=click.INT, help="Port to bind to", default=3003)
|
@click.option("-p", "--port", type=click.INT, help="Port to bind to.")
|
||||||
def cli(address: T.Text, port: int) -> None:
|
@click.option(
|
||||||
run(address, port)
|
"-c",
|
||||||
|
"--conf",
|
||||||
|
type=click.Path(exists=False),
|
||||||
|
help="A path to a configuration file.",
|
||||||
|
)
|
||||||
|
def cli(
|
||||||
|
address: T.Optional[T.Text],
|
||||||
|
port: T.Optional[int] = None,
|
||||||
|
conf: T.Optional[T.Text] = None,
|
||||||
|
) -> None:
|
||||||
|
run(address, port, conf)
|
||||||
|
|
93
api/conf.py
Normal file
93
api/conf.py
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
import os
|
||||||
|
from pprint import pprint
|
||||||
|
import typing as T
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from api import DEBUG, BASE_URI
|
||||||
|
|
||||||
|
|
||||||
|
def read_conf(path: T.Optional[T.Text]) -> T.Dict:
|
||||||
|
"""
|
||||||
|
Read the configuration from the provided path.
|
||||||
|
Such configuration may provide the following information:
|
||||||
|
---
|
||||||
|
base_uri: <base uri of the target instance>
|
||||||
|
debug: <bool, set debug on, defaults to false>
|
||||||
|
headless: <bool, use headless mode, defaults to true>
|
||||||
|
log:
|
||||||
|
level: <may be DEBUG, INFO, WARN, ..., defaults at INFO>
|
||||||
|
syslog: <bool, whether to redirect to standard syslog, defaults to false>
|
||||||
|
http:
|
||||||
|
bind_addr: <a list of addresses to bind to>
|
||||||
|
port: <int, the port to bind to>
|
||||||
|
cookie_name: <defaults to BOTZ_SESSION>
|
||||||
|
cookie_secure: <bool, whether to set Secure cookie flag, defaults to true>
|
||||||
|
cors_allow: <an optional single allowed Cross Origin domain>
|
||||||
|
"""
|
||||||
|
if path is None:
|
||||||
|
path = seek_path()
|
||||||
|
if path is not None:
|
||||||
|
with open(path) as f:
|
||||||
|
conf = yaml.safe_load(f)
|
||||||
|
else:
|
||||||
|
conf = {}
|
||||||
|
if "base_uri" not in conf:
|
||||||
|
if BASE_URI is None:
|
||||||
|
raise RuntimeError("Missing base_uri")
|
||||||
|
conf["base_uri"] = BASE_URI
|
||||||
|
if "debug" not in conf:
|
||||||
|
conf["debug"] = DEBUG
|
||||||
|
if "headless" not in conf:
|
||||||
|
conf["headless"] = True
|
||||||
|
conf = validate_log_conf(conf)
|
||||||
|
conf = validate_http_log(conf)
|
||||||
|
pprint(conf)
|
||||||
|
return conf
|
||||||
|
|
||||||
|
|
||||||
|
def seek_path() -> T.Optional[T.Text]:
|
||||||
|
"""
|
||||||
|
Seeks the path to a config file, in the following order:
|
||||||
|
- $BOTZ_CONFIG
|
||||||
|
- $PWD/.botz.yaml
|
||||||
|
- ~/.botz.yaml
|
||||||
|
- /etc/botz/conf.yaml
|
||||||
|
"""
|
||||||
|
paths = [
|
||||||
|
os.path.join(os.path.curdir, ".botz.yaml"),
|
||||||
|
os.path.expanduser("~/.botz.yaml"),
|
||||||
|
"/etc/botz/conf.yaml",
|
||||||
|
]
|
||||||
|
env = os.environ.get("BOTZ_CONFIG")
|
||||||
|
if env is not None:
|
||||||
|
paths.insert(0, env)
|
||||||
|
for path in paths:
|
||||||
|
if os.path.exists(path):
|
||||||
|
return path
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def validate_log_conf(conf: T.Dict[T.Text, T.Any]) -> T.Dict[T.Text, T.Any]:
|
||||||
|
if "log" not in conf:
|
||||||
|
conf["log"] = {}
|
||||||
|
if conf["log"].get("level") is None:
|
||||||
|
conf["log"]["level"] = "INFO"
|
||||||
|
if conf["log"].get("syslog") is None:
|
||||||
|
conf["log"]["syslog"] = False
|
||||||
|
return conf
|
||||||
|
|
||||||
|
|
||||||
|
def validate_http_log(conf: T.Dict[T.Text, T.Any]) -> T.Dict[T.Text, T.Any]:
|
||||||
|
if "http" not in conf:
|
||||||
|
conf["http"] = {}
|
||||||
|
if conf["http"].get("bind_addr") is None:
|
||||||
|
conf["http"]["bind_addr"] = ["127.0.0.1"]
|
||||||
|
if conf["http"].get("port") is None:
|
||||||
|
conf["http"]["port"] = 3003
|
||||||
|
if conf["http"].get("cookie_name") is None:
|
||||||
|
conf["http"]["cookie_name"] = "BOTZ_SESSION"
|
||||||
|
if conf["http"].get("cookie_secure") is None:
|
||||||
|
conf["http"]["cookie_secure"] = True
|
||||||
|
return conf
|
Loading…
Reference in New Issue
Block a user