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(
|
||||
|
@ -13,10 +13,10 @@ setup(
|
|||
author_email='unit@paranoici.org',
|
||||
|
||||
package_dir={'': 'src'},
|
||||
packages=['phi', 'phi.api', 'phi.ldap'],
|
||||
scripts=['src/phid'],
|
||||
packages=find_packages(),
|
||||
entry_points={"console_scripts": ["phid=phi.app:cli"]},
|
||||
|
||||
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']
|
||||
)
|
||||
|
|
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 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
|
||||
|
||||
log = get_logger(__name__)
|
||||
|
||||
|
||||
def setup_app(config):
|
||||
loop = get_event_loop()
|
||||
|
||||
app = web.Application(loop=loop)
|
||||
app['config'] = config
|
||||
app["config"] = config
|
||||
|
||||
api = api_app(config)
|
||||
app.add_subapp('/api', api)
|
||||
app.add_subapp("/api", api)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
def run_app(app):
|
||||
web.run_app(app,
|
||||
host=app['config']['core']['listen'].get('host', '127.0.0.1'),
|
||||
port=app['config']['core']['listen'].get('port', '8080'))
|
||||
web.run_app(
|
||||
app,
|
||||
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 pkg_resources
|
||||
import yaml
|
||||
|
||||
|
||||
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_PATHS = ['./',
|
||||
'~/.config/' + NAME + '/',
|
||||
|
@ -13,12 +70,18 @@ CONFIG_FILES = [os.path.join(p, CONFIG_FILE)
|
|||
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
|
||||
|
||||
:param config_path: optional path to a config file.
|
||||
|
||||
:returns: (path, config)
|
||||
: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:
|
||||
try:
|
||||
with open(f, 'r') as c:
|
||||
|
@ -30,6 +93,47 @@ def get_config():
|
|||
# accessible or if the file is not present at all
|
||||
# in any of CONFIG_PATHS.
|
||||
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:
|
||||
raise FileNotFoundError("Could not find {} in any of {}."
|
||||
.format(CONFIG_FILE, ', '.join(CONFIG_PATHS)))
|
||||
if main is not None:
|
||||
_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