1
0
mirror of https://github.com/checktheroads/hyperglass synced 2024-05-11 05:55:08 +00:00

add fastapi docs config model

This commit is contained in:
checktheroads
2020-01-20 10:17:52 -07:00
parent abcb87de46
commit 65ccce5aff
5 changed files with 92 additions and 26 deletions

View File

@@ -4,7 +4,7 @@
import re import re
# Third Party Imports # Third Party Imports
from pydantic import BaseSettings from pydantic import BaseModel
def clean_name(_name): def clean_name(_name):
@@ -25,7 +25,7 @@ def clean_name(_name):
return _scrubbed.lower() return _scrubbed.lower()
class HyperglassModel(BaseSettings): class HyperglassModel(BaseModel):
"""Base model for all hyperglass configuration models.""" """Base model for all hyperglass configuration models."""
pass pass
@@ -51,3 +51,29 @@ class HyperglassModelExtra(HyperglassModel):
"""Default pydantic configuration.""" """Default pydantic configuration."""
extra = "allow" extra = "allow"
class AnyUri(str):
"""Custom field type for HTTP URI, e.g. /example."""
@classmethod
def __get_validators__(cls):
"""Pydantic custim field method."""
yield cls.validate
@classmethod
def validate(cls, value):
"""Ensure URI string contains a leading forward-slash."""
uri_regex = re.compile(r"^(\/.*)$")
if not isinstance(value, str):
raise TypeError("AnyUri type must be a string")
match = uri_regex.fullmatch(value)
if not match:
raise ValueError(
"Invalid format. A URI must begin with a forward slash, e.g. '/example'"
)
return cls(match.group())
def __repr__(self):
"""Stringify custom field representation."""
return f"AnyUri({super().__repr__()})"

View File

@@ -18,6 +18,7 @@ from pydantic import validator
# Project Imports # Project Imports
from hyperglass.configuration.models._utils import HyperglassModel from hyperglass.configuration.models._utils import HyperglassModel
from hyperglass.configuration.models.docs import Docs
from hyperglass.configuration.models.opengraph import OpenGraph from hyperglass.configuration.models.opengraph import OpenGraph
@@ -46,6 +47,7 @@ class General(HyperglassModel):
"isp", "isp",
] ]
opengraph: OpenGraph = OpenGraph() opengraph: OpenGraph = OpenGraph()
docs: Docs = Docs()
google_analytics: StrictStr = "" google_analytics: StrictStr = ""
redis_host: StrictStr = "localhost" redis_host: StrictStr = "localhost"
redis_port: StrictInt = 6379 redis_port: StrictInt = 6379
@@ -54,6 +56,7 @@ class General(HyperglassModel):
listen_address: Optional[Union[IPvAnyAddress, StrictStr]] listen_address: Optional[Union[IPvAnyAddress, StrictStr]]
listen_port: StrictInt = 8001 listen_port: StrictInt = 8001
log_file: Optional[FilePath] log_file: Optional[FilePath]
cors_origins: List[StrictStr] = []
@validator("listen_address", pre=True, always=True) @validator("listen_address", pre=True, always=True)
def validate_listen_address(cls, value, values): def validate_listen_address(cls, value, values):

View File

@@ -1,7 +1,6 @@
"""Hyperglass Front End.""" """Hyperglass Front End."""
# Standard Library Imports # Standard Library Imports
import asyncio
import os import os
import tempfile import tempfile
import time import time
@@ -17,12 +16,14 @@ from prometheus_client import Counter
from prometheus_client import generate_latest from prometheus_client import generate_latest
from prometheus_client import multiprocess from prometheus_client import multiprocess
from starlette.exceptions import HTTPException as StarletteHTTPException from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.middleware.cors import CORSMiddleware
from starlette.requests import Request from starlette.requests import Request
from starlette.responses import PlainTextResponse from starlette.responses import PlainTextResponse
from starlette.responses import UJSONResponse from starlette.responses import UJSONResponse
from starlette.staticfiles import StaticFiles from starlette.staticfiles import StaticFiles
# Project Imports # Project Imports
from hyperglass import __version__
from hyperglass.configuration import frontend_params from hyperglass.configuration import frontend_params
from hyperglass.configuration import params from hyperglass.configuration import params
from hyperglass.exceptions import AuthError from hyperglass.exceptions import AuthError
@@ -37,6 +38,7 @@ from hyperglass.execution.execute import Execute
from hyperglass.models.query import Query from hyperglass.models.query import Query
from hyperglass.util import check_python from hyperglass.util import check_python
from hyperglass.util import log from hyperglass.util import log
from hyperglass.util import write_env
# Verify Python version meets minimum requirement # Verify Python version meets minimum requirement
try: try:
@@ -57,14 +59,41 @@ IMAGES_DIR = STATIC_DIR / "images"
NEXT_DIR = UI_DIR / "_next" NEXT_DIR = UI_DIR / "_next"
log.debug(f"Static Files: {STATIC_DIR}") log.debug(f"Static Files: {STATIC_DIR}")
docs_mode_map = {"swagger": "docs_url", "redoc": "redoc_url"}
docs_config = {"docs_url": None, "redoc_url": None}
if params.general.docs.enable:
if params.general.docs.mode == "swagger":
docs_config["docs_url"] = params.general.docs.uri
docs_config["redoc_url"] = None
elif params.general.docs.mode == "redoc":
docs_config["docs_url"] = None
docs_config["redoc_url"] = params.general.docs.uri
# Main App Definition # Main App Definition
app = FastAPI() app = FastAPI(
debug=params.general.debug,
title=params.general.site_title,
description=params.general.site_description,
version=__version__,
**docs_config,
)
app.mount("/ui", StaticFiles(directory=UI_DIR), name="ui") app.mount("/ui", StaticFiles(directory=UI_DIR), name="ui")
app.mount("/_next", StaticFiles(directory=NEXT_DIR), name="_next") app.mount("/_next", StaticFiles(directory=NEXT_DIR), name="_next")
app.mount("/images", StaticFiles(directory=IMAGES_DIR), name="images") app.mount("/images", StaticFiles(directory=IMAGES_DIR), name="images")
app.mount("/ui/images", StaticFiles(directory=IMAGES_DIR), name="ui/images") app.mount("/ui/images", StaticFiles(directory=IMAGES_DIR), name="ui/images")
APP_PARAMS = { # CORS Configuration
app.add_middleware(
CORSMiddleware,
allow_origins=params.general.cors_origins,
allow_methods=["*"],
allow_headers=["*"],
)
ASGI_PARAMS = {
"host": str(params.general.listen_address), "host": str(params.general.listen_address),
"port": params.general.listen_port, "port": params.general.listen_port,
"debug": params.general.debug, "debug": params.general.debug,
@@ -72,7 +101,7 @@ APP_PARAMS = {
# Redis Config # Redis Config
redis_config = { redis_config = {
"host": params.general.redis_host, "host": str(params.general.redis_host),
"port": params.general.redis_port, "port": params.general.redis_port,
"decode_responses": True, "decode_responses": True,
} }
@@ -80,6 +109,7 @@ redis_config = {
r_cache = aredis.StrictRedis(db=params.features.cache.redis_id, **redis_config) r_cache = aredis.StrictRedis(db=params.features.cache.redis_id, **redis_config)
@app.on_event("startup")
async def check_redis(): async def check_redis():
"""Ensure Redis is running before starting server. """Ensure Redis is running before starting server.
@@ -89,19 +119,29 @@ async def check_redis():
Returns: Returns:
{bool} -- True if Redis is running. {bool} -- True if Redis is running.
""" """
redis_host = redis_config["host"]
redis_port = redis_config["port"]
try: try:
await r_cache.echo("hyperglass test") await r_cache.echo("hyperglass test")
# await r_limiter.echo("hyperglass test")
except Exception: except Exception:
raise HyperglassError( raise HyperglassError(
f"Redis isn't running at: {redis_config['host']}:{redis_config['port']}", f"Redis isn't running at: {redis_host}:{redis_port}", alert="danger"
alert="danger",
) from None ) from None
log.debug(f"Redis is running at: {redis_host}:{redis_port}")
return True return True
# Verify Redis is running @app.on_event("startup")
asyncio.run(check_redis()) async def write_env_variables():
"""Write environment varibles for Next.js/Node.
Returns:
{bool} -- True if successful
"""
result = await write_env({"NODE_ENV": "production", "_HYPERGLASS_URL_": "/"})
if result:
log.debug(result)
return True
# Prometheus Config # Prometheus Config
@@ -141,20 +181,16 @@ async def metrics(request):
@app.exception_handler(StarletteHTTPException) @app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request, exc): async def http_exception_handler(request, exc):
"""Handle application errors.""" """Handle web server errors."""
return UJSONResponse( return UJSONResponse(
{ {"output": exc.detail, "alert": "danger", "keywords": []},
"output": exc.detail.get("message", params.messages.general),
"alert": exc.detail.get("alert", "error"),
"keywords": exc.detail.get("keywords", []),
},
status_code=exc.status_code, status_code=exc.status_code,
) )
@app.exception_handler(HyperglassError) @app.exception_handler(HyperglassError)
async def http_exception_handler(request, exc): async def http_exception_handler(request, exc):
"""Handle request validation errors.""" """Handle application errors."""
return UJSONResponse( return UJSONResponse(
{"output": exc.message, "alert": exc.alert, "keywords": exc.keywords}, {"output": exc.message, "alert": exc.alert, "keywords": exc.keywords},
status_code=400, status_code=400,

View File

@@ -4,9 +4,9 @@
def start(): def start():
"""Start the web server with Uvicorn ASGI.""" """Start the web server with Uvicorn ASGI."""
import uvicorn import uvicorn
from hyperglass.hyperglass import app, APP_PARAMS from hyperglass.hyperglass import app, ASGI_PARAMS
uvicorn.run(app, **APP_PARAMS) uvicorn.run(app, **ASGI_PARAMS)
app = start() app = start()

View File

@@ -611,21 +611,22 @@ def build_ui():
def dev_server(host, port, build): def dev_server(host, port, build):
"""Renders theme and web build, then starts dev web server""" """Renders theme and web build, then starts dev web server"""
try: try:
from hyperglass.hyperglass import app, APP_PARAMS from hyperglass.hyperglass import app, ASGI_PARAMS
except ImportError as import_error: except ImportError as import_error:
raise click.ClickException( raise click.ClickException(
click.style("✗ Error importing hyperglass: ", fg="red", bold=True) click.style("✗ Error importing hyperglass: ", fg="red", bold=True)
+ click.style(import_error, fg="blue") + click.style(import_error, fg="blue")
) )
asgi_params = ASGI_PARAMS.copy()
if host is not None: if host is not None:
APP_PARAMS["host"] = host asgi_params["host"] = host
if port is not None: if port is not None:
APP_PARAMS["port"] = port asgi_params["port"] = port
write_env_variables( write_env_variables(
{ {
"NODE_ENV": "development", "NODE_ENV": "development",
"_HYPERGLASS_URL_": f'http://{APP_PARAMS["host"]}:{APP_PARAMS["port"]}/', "_HYPERGLASS_URL_": f'http://{asgi_params["host"]}:{asgi_params["port"]}/',
} }
) )
if build: if build:
@@ -637,9 +638,9 @@ def dev_server(host, port, build):
+ click.style(e, fg="white") + click.style(e, fg="white")
) from None ) from None
if build_complete: if build_complete:
start_dev_server(app, APP_PARAMS) start_dev_server(app, asgi_params)
if not build: if not build:
start_dev_server(app, APP_PARAMS) start_dev_server(app, asgi_params)
@hg.command("migrate-configs", help="Copy YAML examples to usable config files") @hg.command("migrate-configs", help="Copy YAML examples to usable config files")