Introduce configuration in server app.
This commit is contained in:
parent
50f0b64182
commit
7d6b445678
|
@ -1,5 +1,7 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
from distutils.util import strtobool
|
||||
import os
|
||||
|
||||
# TODO: create config module and put this constant there.
|
||||
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
|
||||
from cryptography import fernet
|
||||
import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
import typing as T
|
||||
|
||||
|
@ -17,15 +18,19 @@ from aiohttp_session.cookie_storage import EncryptedCookieStorage
|
|||
|
||||
from bot_z.async_operator import AsyncOperator
|
||||
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.setLevel(os.environ.get("BOTZ_LOGLEVEL", logging.INFO))
|
||||
alog.setLevel(os.environ.get("BOTZ_LOGLEVEL", level))
|
||||
h = logging.StreamHandler()
|
||||
f = logging.Formatter("%(levelname)s [%(name)s] -> %(message)s")
|
||||
h.setFormatter(f)
|
||||
alog.addHandler(h)
|
||||
if syslog:
|
||||
sh = logging.handlers.SysLogHandler()
|
||||
alog.addHandler(sh)
|
||||
return alog
|
||||
|
||||
|
||||
|
@ -34,28 +39,63 @@ def init_secret() -> bytes:
|
|||
return base64.urlsafe_b64decode(fernet_key)
|
||||
|
||||
|
||||
def setup_session(app: web.Application):
|
||||
def setup_session(app: web.Application, secure: bool):
|
||||
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."""
|
||||
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)
|
||||
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)
|
||||
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.option(
|
||||
"-a",
|
||||
"--address",
|
||||
type=click.STRING,
|
||||
help="Address to bind the server to.",
|
||||
default="127.0.0.1",
|
||||
"-a", "--address", type=click.STRING, help="Address to bind the server to."
|
||||
)
|
||||
@click.option("-p", "--port", type=click.INT, help="Port to bind to", default=3003)
|
||||
def cli(address: T.Text, port: int) -> None:
|
||||
run(address, port)
|
||||
@click.option("-p", "--port", type=click.INT, help="Port to bind to.")
|
||||
@click.option(
|
||||
"-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