Refactor entrypoint to use click.
This commit is contained in:
parent
bec6123d47
commit
80fb51f7de
8
setup.py
8
setup.py
|
@ -1,4 +1,4 @@
|
||||||
from setuptools import setup
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
|
@ -13,10 +13,10 @@ setup(
|
||||||
author_email='unit@paranoici.org',
|
author_email='unit@paranoici.org',
|
||||||
|
|
||||||
package_dir={'': 'src'},
|
package_dir={'': 'src'},
|
||||||
packages=['phi', 'phi.api', 'phi.ldap'],
|
packages=find_packages(),
|
||||||
scripts=['src/phid'],
|
entry_points={"console_scripts": ["phid=phi.app:cli"]},
|
||||||
|
|
||||||
setup_requires=['pytest-runner'],
|
setup_requires=['pytest-runner'],
|
||||||
install_requires=['aiohttp==2.3.8', 'pyYAML', 'ldap3'],
|
install_requires=['aiohttp==2.3.8', 'click==7.0', 'pyYAML', 'ldap3'],
|
||||||
tests_require=['pytest', 'pytest-aiohttp']
|
tests_require=['pytest', 'pytest-aiohttp']
|
||||||
)
|
)
|
||||||
|
|
202
src/phi/app.py
202
src/phi/app.py
|
@ -1,22 +1,214 @@
|
||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
import pkg_resources
|
||||||
|
|
||||||
from asyncio import get_event_loop
|
from asyncio import get_event_loop
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
import click
|
||||||
|
from pprint import pformat as pp
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from phi.config import get_config, merge_config
|
||||||
|
from phi.logging import setup_logging, get_logger
|
||||||
from phi.api.app import api_app
|
from phi.api.app import api_app
|
||||||
|
|
||||||
|
log = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def setup_app(config):
|
def setup_app(config):
|
||||||
loop = get_event_loop()
|
loop = get_event_loop()
|
||||||
|
|
||||||
app = web.Application(loop=loop)
|
app = web.Application(loop=loop)
|
||||||
app['config'] = config
|
app["config"] = config
|
||||||
|
|
||||||
api = api_app(config)
|
api = api_app(config)
|
||||||
app.add_subapp('/api', api)
|
app.add_subapp("/api", api)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
def run_app(app):
|
def run_app(app):
|
||||||
web.run_app(app,
|
web.run_app(
|
||||||
host=app['config']['core']['listen'].get('host', '127.0.0.1'),
|
app,
|
||||||
port=app['config']['core']['listen'].get('port', '8080'))
|
host=app["config"]["core"]["listen"].get("host", "127.0.0.1"),
|
||||||
|
port=app["config"]["core"]["listen"].get("port", "8080"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_port(ctx, param, value):
|
||||||
|
"""
|
||||||
|
Callback to validate provided port.
|
||||||
|
"""
|
||||||
|
if 0 < value or value > 65535:
|
||||||
|
raise click.BadParameter(
|
||||||
|
"Provided value is not in the range 0-65535: {} [type: {}]".format(
|
||||||
|
value, type(value)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
@click.command(help="phid is the main application daemon.")
|
||||||
|
@click.option(
|
||||||
|
"--config",
|
||||||
|
"config_path",
|
||||||
|
type=click.Path(exists=True),
|
||||||
|
help="Path to a valid config file.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"-H",
|
||||||
|
"--host",
|
||||||
|
type=click.STRING,
|
||||||
|
default="localhost",
|
||||||
|
help='Address to which the application bounds. Defaults to "localhost".',
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"-p",
|
||||||
|
"--port",
|
||||||
|
type=click.INT,
|
||||||
|
default=8080,
|
||||||
|
help="Port to which the application bounds. Defaults to 8080.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--ldap-host",
|
||||||
|
"ldap_host",
|
||||||
|
type=click.STRING,
|
||||||
|
default="localhost",
|
||||||
|
help='Address of the LDAP server to connect to. Defaults to "localhost".',
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--ldap-port",
|
||||||
|
"ldap_port",
|
||||||
|
type=click.INT,
|
||||||
|
default=389,
|
||||||
|
help="Port where is exposed the LDAP server to connect to. Defaults to 389.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--ldap-crypt",
|
||||||
|
"ldap_crypt",
|
||||||
|
is_flag=True,
|
||||||
|
default=True,
|
||||||
|
help="Connect to the LDAP server using TLSv1.2. Defaults to True.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--ldap-tls-validate",
|
||||||
|
"ldap_tls_validate",
|
||||||
|
is_flag=True,
|
||||||
|
default=True,
|
||||||
|
help="Toggle checking of TLS cert against the provided name. Defaults to True.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--ldap-tls-ca",
|
||||||
|
"ldap_tls_ca",
|
||||||
|
type=click.Path(exists=True),
|
||||||
|
help="Toggle checking of TLS cert against the provided name. Defaults to True.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--ldap-base-dn", "ldap_base_dn", type=click.STRING, help="The LDAP base_dn to use."
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--ldap-username",
|
||||||
|
"ldap_username",
|
||||||
|
type=click.STRING,
|
||||||
|
help="The username to use to connect to the LDAP server.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--ldap-password",
|
||||||
|
"ldap_password",
|
||||||
|
type=click.STRING,
|
||||||
|
help="The password to use to connect to the LDAP server. "
|
||||||
|
"THIS CAN BE READ BY OTHER PROCESSES. NEVER USE IN PRODUCTION!",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--log-conf",
|
||||||
|
"log_conf",
|
||||||
|
type=click.Path(exists=True),
|
||||||
|
help="Path to a yaml configuration for the logger.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--debug",
|
||||||
|
"debug",
|
||||||
|
is_flag=True,
|
||||||
|
default=False,
|
||||||
|
help="Set the log level to debug.",
|
||||||
|
)
|
||||||
|
def cli(
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
config_path=None,
|
||||||
|
ldap_host=None,
|
||||||
|
ldap_port=None,
|
||||||
|
ldap_crypt=True,
|
||||||
|
ldap_tls_validate=True,
|
||||||
|
ldap_tls_ca=None,
|
||||||
|
ldap_base_dn=None,
|
||||||
|
ldap_username=None,
|
||||||
|
ldap_password=None,
|
||||||
|
log_conf=None,
|
||||||
|
debug=False,
|
||||||
|
):
|
||||||
|
cli_config = prepare_config_from_cli(
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
ldap_host,
|
||||||
|
ldap_port,
|
||||||
|
ldap_crypt,
|
||||||
|
ldap_tls_validate,
|
||||||
|
ldap_tls_ca,
|
||||||
|
ldap_base_dn,
|
||||||
|
ldap_username,
|
||||||
|
ldap_password,
|
||||||
|
log_conf,
|
||||||
|
debug,
|
||||||
|
)
|
||||||
|
config_file, file_config = get_config(config_path)
|
||||||
|
config = merge_config(cli_config, file_config)
|
||||||
|
if debug:
|
||||||
|
set_to_debug(config)
|
||||||
|
# Beware that everything happened until now
|
||||||
|
# could not possibly get logged.
|
||||||
|
setup_logging(config.get("logging", {}))
|
||||||
|
if config_file:
|
||||||
|
log.debug("Config file found at: %s", config_file)
|
||||||
|
log.debug("{}".format(pp(file_config)))
|
||||||
|
log.debug("CLI config:\n{}".format(pp(cli_config)))
|
||||||
|
log.info("Starting app with config:\n{}".format(pp(config)))
|
||||||
|
|
||||||
|
app = setup_app(config)
|
||||||
|
run_app(app)
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_config_from_cli(
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
ldap_host=None,
|
||||||
|
ldap_port=None,
|
||||||
|
ldap_crypt=True,
|
||||||
|
ldap_tls_validate=True,
|
||||||
|
ldap_tls_ca=None,
|
||||||
|
ldap_base_dn=None,
|
||||||
|
ldap_username=None,
|
||||||
|
ldap_password=None,
|
||||||
|
log_conf=None,
|
||||||
|
debug=False,
|
||||||
|
):
|
||||||
|
_core = {"listen": {"host": host, "port": port}}
|
||||||
|
_ldap = {
|
||||||
|
"encryption": "TLSv1.2" if ldap_crypt else None,
|
||||||
|
"validate": ldap_tls_validate,
|
||||||
|
"ca_certs": ldap_tls_ca,
|
||||||
|
"username": ldap_username,
|
||||||
|
"password": ldap_password,
|
||||||
|
"base_dn": ldap_base_dn,
|
||||||
|
}
|
||||||
|
_logging = {}
|
||||||
|
if log_conf:
|
||||||
|
with open(log_conf) as l:
|
||||||
|
_logging = yaml.safe_load(l)
|
||||||
|
|
||||||
|
return {"core": _core, "ldap": _ldap, "logging": _logging}
|
||||||
|
|
||||||
|
|
||||||
|
def set_to_debug(conf):
|
||||||
|
for logger, log_conf in conf["logging"]["loggers"].items():
|
||||||
|
log_conf["level"] = "DEBUG"
|
||||||
|
conf["logging"]["loggers"][logger] = log_conf
|
||||||
|
|
|
@ -1,9 +1,66 @@
|
||||||
import os.path
|
import os.path
|
||||||
|
import pkg_resources
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
NAME = 'phi'
|
NAME = 'phi'
|
||||||
|
|
||||||
|
DEFAULT_CONFIG = {
|
||||||
|
"core": {
|
||||||
|
"listen": {
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 8080,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ldap": {
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 389,
|
||||||
|
"encryption": "TLSv1.2",
|
||||||
|
"ciphers": "HIGH",
|
||||||
|
"validate": True,
|
||||||
|
"ca_certs": pkg_resources.resource_filename(NAME, "openldap/cert.pem"),
|
||||||
|
"username": None,
|
||||||
|
"password": None,
|
||||||
|
"base_dn": None,
|
||||||
|
"attribute_id": "uid",
|
||||||
|
"attribute_mail": "mail",
|
||||||
|
},
|
||||||
|
"logging": {
|
||||||
|
"version": 1,
|
||||||
|
"formatters": {
|
||||||
|
"default": {"format": "[%(name)s %(levelname)s] %(message)s"}
|
||||||
|
},
|
||||||
|
"handlers": {
|
||||||
|
"console": {
|
||||||
|
"class": "logging.StreamHandler",
|
||||||
|
"formatter": "default",
|
||||||
|
"stream": "ext://sys.stdout",
|
||||||
|
},
|
||||||
|
"file": {
|
||||||
|
"class": "logging.FileHandler",
|
||||||
|
"formatter": "default",
|
||||||
|
"filename": "phi.log",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"loggers": {
|
||||||
|
"phi": {
|
||||||
|
"level": "INFO",
|
||||||
|
"handlers": ["console", "file"],
|
||||||
|
},
|
||||||
|
"aiohttp": {
|
||||||
|
"level": "INFO",
|
||||||
|
"handlers": ["console", "file"],
|
||||||
|
},
|
||||||
|
"ldap3": {
|
||||||
|
"level": "WARNING",
|
||||||
|
"handlers": ["console", "file"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DUMMY_CONFIG = {"core": {}, "ldap": {}, "logging": {}}
|
||||||
|
|
||||||
CONFIG_FILE = 'config.yml'
|
CONFIG_FILE = 'config.yml'
|
||||||
CONFIG_PATHS = ['./',
|
CONFIG_PATHS = ['./',
|
||||||
'~/.config/' + NAME + '/',
|
'~/.config/' + NAME + '/',
|
||||||
|
@ -13,12 +70,18 @@ CONFIG_FILES = [os.path.join(p, CONFIG_FILE)
|
||||||
for p in CONFIG_PATHS]
|
for p in CONFIG_PATHS]
|
||||||
|
|
||||||
|
|
||||||
def get_config():
|
def get_config(config_path=None):
|
||||||
"""Return the path of the found configuration file and its content
|
"""Return the path of the found configuration file and its content
|
||||||
|
|
||||||
|
:param config_path: optional path to a config file.
|
||||||
|
|
||||||
:returns: (path, config)
|
:returns: (path, config)
|
||||||
:rtype: (str, dict)
|
:rtype: (str, dict)
|
||||||
"""
|
"""
|
||||||
|
if config_path:
|
||||||
|
with open(config_path) as c:
|
||||||
|
config = yaml.safe_load(c)
|
||||||
|
return config_path, config
|
||||||
for f in CONFIG_FILES:
|
for f in CONFIG_FILES:
|
||||||
try:
|
try:
|
||||||
with open(f, 'r') as c:
|
with open(f, 'r') as c:
|
||||||
|
@ -30,6 +93,47 @@ def get_config():
|
||||||
# accessible or if the file is not present at all
|
# accessible or if the file is not present at all
|
||||||
# in any of CONFIG_PATHS.
|
# in any of CONFIG_PATHS.
|
||||||
pass
|
pass
|
||||||
|
return None, DUMMY_CONFIG
|
||||||
|
|
||||||
|
|
||||||
|
def merge_config(cli_config, file_config):
|
||||||
|
"""
|
||||||
|
Merge the cli-provided and file-provided config.
|
||||||
|
"""
|
||||||
|
return recursive_merge(cli_config, file_config)
|
||||||
|
|
||||||
|
|
||||||
|
def _init_with_shape_of(element):
|
||||||
|
if isinstance(element, dict):
|
||||||
|
return {}
|
||||||
|
elif isinstance(element, list):
|
||||||
|
return []
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def recursive_merge(main_config, aux_config):
|
||||||
|
def _recursive_merge(main, aux, default, key, _ret_config):
|
||||||
|
if isinstance(default, dict):
|
||||||
|
_sub_conf = {}
|
||||||
|
for k, v in default.items():
|
||||||
|
_main = main[k] if k in main else _init_with_shape_of(v)
|
||||||
|
_aux = aux[k] if k in aux else _init_with_shape_of(v)
|
||||||
|
_recursive_merge(_main, _aux, v, k, _sub_conf)
|
||||||
|
_ret_config[key] = _sub_conf
|
||||||
|
elif isinstance(default, list):
|
||||||
|
_main = main.copy()
|
||||||
|
if aux is not None:
|
||||||
|
_main.extend(aux)
|
||||||
|
_ret_config[key] = list(set(_main))
|
||||||
else:
|
else:
|
||||||
raise FileNotFoundError("Could not find {} in any of {}."
|
if main is not None:
|
||||||
.format(CONFIG_FILE, ', '.join(CONFIG_PATHS)))
|
_ret_config[key] = main
|
||||||
|
elif aux is not None:
|
||||||
|
_ret_config[key] = aux
|
||||||
|
else:
|
||||||
|
_ret_config[key] = default
|
||||||
|
|
||||||
|
_config = {}
|
||||||
|
_recursive_merge(main_config, aux_config, DEFAULT_CONFIG, "ROOT", _config)
|
||||||
|
|
||||||
|
return _config["ROOT"]
|
||||||
|
|
22
src/phid
22
src/phid
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
from pprint import pformat as pp
|
|
||||||
|
|
||||||
from phi.config import get_config
|
|
||||||
from phi.logging import setup_logging, get_logger
|
|
||||||
from phi.app import setup_app, run_app
|
|
||||||
|
|
||||||
log = get_logger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
config_file, config = get_config()
|
|
||||||
|
|
||||||
# Beware that everything happened until now
|
|
||||||
# could not possibly get logged.
|
|
||||||
setup_logging(config.get('logging', {}))
|
|
||||||
|
|
||||||
log.info("Found configuration at '{}':\n{}"
|
|
||||||
.format(config_file, pp(config)))
|
|
||||||
|
|
||||||
app = setup_app(config)
|
|
||||||
run_app(app)
|
|
Loading…
Reference in New Issue
Block a user