From 96df5469de4bba40aa552ef1fabc2af1239c9abb Mon Sep 17 00:00:00 2001 From: checktheroads Date: Fri, 14 Feb 2020 16:30:40 -0700 Subject: [PATCH] detect & give user option to set app runtime directory --- hyperglass/__init__.py | 59 +++++++++++++++++++++++----- hyperglass/api/__init__.py | 12 +++++- hyperglass/api/events.py | 2 + hyperglass/configuration/__init__.py | 46 +++++----------------- 4 files changed, 70 insertions(+), 49 deletions(-) diff --git a/hyperglass/__init__.py b/hyperglass/__init__.py index 6f76369..2e0fff1 100644 --- a/hyperglass/__init__.py +++ b/hyperglass/__init__.py @@ -37,23 +37,62 @@ POSSIBILITY OF SUCH DAMAGE. """ # Standard Library -# Standard Library Imports +import os import sys -from datetime import datetime +import getpass +from pathlib import Path # Third Party import uvloop -# Third Party Imports -import stackprinter - # Project -# Project Imports -# flake8: noqa: F401 -from hyperglass import api, cli, util, constants, execution, exceptions, configuration +from hyperglass.constants import METADATA -stackprinter.set_excepthook() +try: + import stackprinter +except ImportError: + pass +else: + if sys.stdout.isatty(): + _style = "darkbg2" + else: + _style = "plaintext" + stackprinter.set_excepthook(style=_style) + +config_path = None + +_CONFIG_PATHS = (Path.home() / "hyperglass", Path("/etc/hyperglass/")) + +for path in _CONFIG_PATHS: + try: + if not isinstance(path, Path): + path = Path(path) + + if path.exists(): + tmp = path / "test.tmp" + tmp.touch() + if tmp.exists(): + config_path = path + tmp.unlink() + break + except Exception: + config_path = None + +if config_path is None: + raise RuntimeError( + """ +No configuration directories were determined to both exist and be readable +by hyperglass. hyperglass is running as user '{un}' (UID '{uid}'), and tried to access +the following directories: +{dir}""".format( + un=getpass.getuser(), + uid=os.getuid(), + dir="\n".join([" - " + str(p) for p in _CONFIG_PATHS]), + ) + ) + +os.environ["hyperglass_directory"] = str(config_path) uvloop.install() -__name__, __version__, __author__, __copyright__, __license__ = constants.METADATA +__name__, __version__, __author__, __copyright__, __license__ = METADATA diff --git a/hyperglass/api/__init__.py b/hyperglass/api/__init__.py index bf7856b..3b0e8b9 100644 --- a/hyperglass/api/__init__.py +++ b/hyperglass/api/__init__.py @@ -19,7 +19,7 @@ from hyperglass.constants import __version__ from hyperglass.api.events import on_startup, on_shutdown from hyperglass.api.routes import docs, query, queries, routers from hyperglass.exceptions import HyperglassError -from hyperglass.configuration import URL_DEV, params +from hyperglass.configuration import URL_DEV, STATIC_PATH, params from hyperglass.api.error_handlers import ( app_handler, http_handler, @@ -35,10 +35,12 @@ from hyperglass.api.models.response import ( WORKING_DIR = Path(__file__).parent STATIC_DIR = WORKING_DIR.parent / "static" -UI_DIR = STATIC_DIR / "ui" IMAGES_DIR = STATIC_DIR / "images" EXAMPLES_DIR = WORKING_DIR / "examples" +UI_DIR = STATIC_PATH / "ui" +CUSTOM_DIR = STATIC_PATH / "custom" + EXAMPLE_DEVICES_PY = EXAMPLES_DIR / "devices.py" EXAMPLE_QUERIES_PY = EXAMPLES_DIR / "queries.py" EXAMPLE_QUERY_PY = EXAMPLES_DIR / "query.py" @@ -59,6 +61,11 @@ if params.docs.enable: elif params.docs.mode == "swagger": DOCS_PARAMS.update({"docs_url": params.docs.uri, "redoc_url": None}) +for directory in (UI_DIR, IMAGES_DIR): + if not directory.exists(): + log.warning("Directory '{d}' does not exist, creating...", d=str(directory)) + directory.mkdir() + # Main App Definition app = FastAPI( debug=params.debug, @@ -200,6 +207,7 @@ if params.docs.enable: log.debug(f"API Docs config: {app.openapi()}") app.mount("/images", StaticFiles(directory=IMAGES_DIR), name="images") +app.mount("/custom", StaticFiles(directory=CUSTOM_DIR), name="custom") app.mount("/", StaticFiles(directory=UI_DIR, html=True), name="ui") diff --git a/hyperglass/api/events.py b/hyperglass/api/events.py index 5a7dac7..21544ac 100644 --- a/hyperglass/api/events.py +++ b/hyperglass/api/events.py @@ -14,6 +14,7 @@ from hyperglass.exceptions import HyperglassError from hyperglass.configuration import ( URL_DEV, URL_PROD, + CONFIG_PATH, REDIS_CONFIG, params, frontend_params, @@ -66,6 +67,7 @@ async def build_ui(): dev_url=URL_DEV, prod_url=URL_PROD, params=frontend_params, + app_path=CONFIG_PATH, ) except RuntimeError as e: raise HTTPException(detail=str(e), status_code=500) diff --git a/hyperglass/configuration/__init__.py b/hyperglass/configuration/__init__.py index f9b08c2..768375d 100644 --- a/hyperglass/configuration/__init__.py +++ b/hyperglass/configuration/__init__.py @@ -5,7 +5,6 @@ import os import copy import math import asyncio -import getpass from pathlib import Path # Third Party @@ -32,15 +31,11 @@ from hyperglass.configuration.models import routers as _routers from hyperglass.configuration.models import commands as _commands from hyperglass.configuration.markdown import get_markdown +CONFIG_PATH = Path(os.environ["hyperglass_directory"]) +log.info("Configuration directory: {d}", d=str(CONFIG_PATH)) + # Project Directories WORKING_DIR = Path(__file__).resolve().parent -CONFIG_PATHS = ( - Path("/etc/hyperglass/"), - Path.home() / "hyperglass", - WORKING_DIR.parent.parent, - WORKING_DIR.parent, - WORKING_DIR, -) CONFIG_FILES = ( ("hyperglass.yaml", False), ("devices.yaml", True), @@ -48,30 +43,6 @@ CONFIG_FILES = ( ) -async def _check_config_paths(): - """Verify supported configuration directories exist and are readable.""" - config_path = None - for path in CONFIG_PATHS: - checked = await check_path(path) - if checked is not None: - config_path = checked - break - if config_path is None: - raise ConfigError( - """ -No configuration directories were determined to both exist and be readable -by hyperglass. hyperglass is running as user '{un}' (UID '{uid}'), and tried to access -the following directories: -{dir}""".format( - un=getpass.getuser(), - uid=os.getuid(), - dir="\n".join([" - " + str(p) for p in CONFIG_PATHS]), - ) - ) - log.info("Configuration directory: {d}", d=str(config_path)) - return config_path - - async def _check_config_files(directory): """Verify config files exist and are readable. @@ -105,7 +76,7 @@ async def _check_config_files(directory): return files -CONFIG_PATH = asyncio.run(_check_config_paths()) +STATIC_PATH = CONFIG_PATH / "static" CONFIG_MAIN, CONFIG_DEVICES, CONFIG_COMMANDS = asyncio.run( _check_config_files(CONFIG_PATH) @@ -171,7 +142,7 @@ async def _config_commands(): async with AIOFile(CONFIG_COMMANDS, "r") as cf: raw = await cf.read() config = yaml.safe_load(raw) or {} - log.debug(f"Unvalidated commands: {config}") + log.debug("Unvalidated commands: {c}", c=config) except (yaml.YAMLError, yaml.MarkedYAMLError) as yaml_error: raise ConfigError(error_msg=str(yaml_error)) from None return config @@ -187,7 +158,7 @@ async def _config_devices(): async with AIOFile(CONFIG_DEVICES, "r") as cf: raw = await cf.read() config = yaml.safe_load(raw) - log.debug(f"Unvalidated device config: {config}") + log.debug("Unvalidated device config: {c}", c=config) except (yaml.YAMLError, yaml.MarkedYAMLError) as yaml_error: raise ConfigError(error_msg=str(yaml_error)) from None return config @@ -197,7 +168,7 @@ user_config = asyncio.run(_config_main()) # Logging Config try: - _debug = user_config["general"]["debug"] + _debug = user_config["debug"] except KeyError: _debug = True @@ -454,6 +425,7 @@ frontend_networks = _build_frontend_networks() frontend_devices = _build_frontend_devices() _frontend_fields = { "debug": ..., + "developer_mode": ..., "primary_asn": ..., "request_timeout": ..., "org_name": ..., @@ -479,7 +451,7 @@ _frontend_params.update( ) frontend_params = _frontend_params -URL_DEV = f"http://localhost:{str(params.listen_port)}/api/" +URL_DEV = f"http://localhost:{str(params.listen_port)}/" URL_PROD = "/api/" REDIS_CONFIG = {