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

Clean up API routes

This commit is contained in:
thatmattlove
2021-09-11 13:56:20 -07:00
parent fb003f3248
commit dc274992b8
4 changed files with 44 additions and 103 deletions

View File

@@ -18,21 +18,12 @@ from fastapi.middleware.gzip import GZipMiddleware
# Project # Project
from hyperglass.log import log from hyperglass.log import log
from hyperglass.util import cpu_count from hyperglass.util import cpu_count
from hyperglass.constants import TRANSPORT_REST, __version__ from hyperglass.constants import __version__
from hyperglass.models.ui import UIParameters from hyperglass.models.ui import UIParameters
from hyperglass.api.events import on_startup, on_shutdown from hyperglass.api.events import on_startup, on_shutdown
from hyperglass.api.routes import ( from hyperglass.api.routes import docs, info, query, router, queries, routers, ui_props
docs,
info,
query,
queries,
routers,
ui_props,
communities,
import_certificate,
)
from hyperglass.exceptions import HyperglassError from hyperglass.exceptions import HyperglassError
from hyperglass.configuration import URL_DEV, STATIC_PATH, params, devices from hyperglass.configuration import URL_DEV, STATIC_PATH, params
from hyperglass.api.error_handlers import ( from hyperglass.api.error_handlers import (
app_handler, app_handler,
http_handler, http_handler,
@@ -211,12 +202,14 @@ app.add_api_route(
) )
app.add_api_route( app.add_api_route(
path="/api/communities", path="/api/devices/{id}",
endpoint=communities, endpoint=router,
methods=["GET"], methods=["GET"],
response_model=List[CommunityResponse], response_model=RoutersResponse,
summary=params.docs.communities.summary, response_class=JSONResponse,
tags=[params.docs.communities.title], summary=params.docs.devices.summary,
description=params.docs.devices.description,
tags=[params.docs.devices.title],
) )
app.add_api_route( app.add_api_route(
@@ -255,15 +248,6 @@ app.add_api_route(
response_model_by_alias=True, response_model_by_alias=True,
) )
# Enable certificate import route only if a device using
# hyperglass-agent is defined.
if [n for n in devices.all_nos if n in TRANSPORT_REST]:
app.add_api_route(
path="/api/import-agent-certificate/",
endpoint=import_certificate,
methods=["POST"],
include_in_schema=False,
)
if params.docs.enable: if params.docs.enable:
app.add_api_route(path=params.docs.uri, endpoint=docs, include_in_schema=False) app.add_api_route(path=params.docs.uri, endpoint=docs, include_in_schema=False)

View File

@@ -14,12 +14,11 @@ from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
# Project # Project
from hyperglass.log import log from hyperglass.log import log
from hyperglass.cache import AsyncCache from hyperglass.cache import AsyncCache
from hyperglass.encode import jwt_decode
from hyperglass.external import Webhook, bgptools from hyperglass.external import Webhook, bgptools
from hyperglass.api.tasks import process_headers, import_public_key from hyperglass.api.tasks import process_headers
from hyperglass.constants import __version__ from hyperglass.constants import __version__
from hyperglass.exceptions import HyperglassError from hyperglass.exceptions import HyperglassError
from hyperglass.models.api import Query, EncodedRequest from hyperglass.models.api import Query
from hyperglass.configuration import REDIS_CONFIG, params, devices, ui_params from hyperglass.configuration import REDIS_CONFIG, params, devices, ui_params
from hyperglass.execution.main import execute from hyperglass.execution.main import execute
@@ -30,15 +29,7 @@ APP_PATH = os.environ["hyperglass_directory"]
async def send_webhook(query_data: Query, request: Request, timestamp: datetime): async def send_webhook(query_data: Query, request: Request, timestamp: datetime):
"""If webhooks are enabled, get request info and send a webhook. """If webhooks are enabled, get request info and send a webhook."""
Args:
query_data (Query): Valid query
request (Request): Starlette/FastAPI request
Returns:
int: Returns 1 regardless of result
"""
try: try:
if params.logging.http is not None: if params.logging.http is not None:
headers = await process_headers(headers=request.headers) headers = await process_headers(headers=request.headers)
@@ -173,47 +164,6 @@ async def query(query_data: Query, request: Request, background_tasks: Backgroun
} }
async def import_certificate(encoded_request: EncodedRequest):
"""Import a certificate from hyperglass-agent."""
# Try to match the requested device name with configured devices
log.debug(
"Attempting certificate import for device '{}'", devices[encoded_request.device]
)
try:
matched_device = devices[encoded_request.device]
except AttributeError:
raise HTTPException(
detail=f"Device {str(encoded_request.device)} not found", status_code=404
)
try:
# Decode JSON Web Token
decoded_request = await jwt_decode(
payload=encoded_request.encoded,
secret=matched_device.credential.password.get_secret_value(),
)
except HyperglassError as decode_error:
raise HTTPException(detail=str(decode_error), status_code=400)
try:
# Write certificate to file
import_public_key(
app_path=APP_PATH,
device_name=matched_device._id,
keystring=decoded_request,
)
except RuntimeError as err:
raise HyperglassError(str(err), level="danger")
log.info("Added public key for {}", encoded_request.device)
return {
"output": f"Added public key for {encoded_request.device}",
"level": "success",
"keywords": [encoded_request.device],
}
async def docs(): async def docs():
"""Serve custom docs.""" """Serve custom docs."""
if params.docs.enable: if params.docs.enable:
@@ -226,27 +176,14 @@ async def docs():
raise HTTPException(detail="Not found", status_code=404) raise HTTPException(detail="Not found", status_code=404)
async def router(id: str):
"""Get a device's API-facing attributes."""
return devices[id].export_api()
async def routers(): async def routers():
"""Serve list of configured routers and attributes.""" """Serve list of configured routers and attributes."""
return [ return devices.export_api()
d.dict(
include={
"name": ...,
"network": ...,
"display_name": ...,
"vrfs": {-1: {"name", "display_name"}},
}
)
for d in devices.objects
]
async def communities():
"""Serve list of configured communities if mode is select."""
if params.queries.bgp_community.mode != "select":
raise HTTPException(detail="BGP community mode is not select", status_code=404)
return [c.export_dict() for c in params.queries.bgp_community.communities]
async def queries(): async def queries():
@@ -260,7 +197,7 @@ async def info():
"name": params.site_title, "name": params.site_title,
"organization": params.org_name, "organization": params.org_name,
"primary_asn": int(params.primary_asn), "primary_asn": int(params.primary_asn),
"version": f"hyperglass {__version__}", "version": __version__,
} }

View File

@@ -178,16 +178,24 @@ class Network(BaseModel):
class RoutersResponse(BaseModel): class RoutersResponse(BaseModel):
"""Response model for /api/devices list items.""" """Response model for /api/devices list items."""
id: StrictStr
name: StrictStr name: StrictStr
network: Network network: StrictStr
vrfs: List[Vrf]
class Config: class Config:
"""Pydantic model configuration.""" """Pydantic model configuration."""
title = "Device" title = "Device"
description = "Per-device attributes" description = "Device attributes"
schema_extra = {"examples": [{"name": "router01-nyc01", "location": "nyc01"}]} schema_extra = {
"examples": [
{
"id": "nyc_router_1",
"name": "NYC Router 1",
"network": "New York City, NY",
}
]
}
class CommunityResponse(BaseModel): class CommunityResponse(BaseModel):

View File

@@ -103,6 +103,14 @@ class Device(HyperglassModel, extra="allow"):
return device_id, {"name": display_name, "display_name": None, **values} return device_id, {"name": display_name, "display_name": None, **values}
def export_api(self) -> Dict[str, Any]:
"""Export API-facing device fields."""
return {
"id": self._id,
"name": self.name,
"network": self.network.display_name,
}
@property @property
def directive_commands(self) -> List[str]: def directive_commands(self) -> List[str]:
"""Get all commands associated with the device.""" """Get all commands associated with the device."""
@@ -278,6 +286,10 @@ class Devices(HyperglassModel, extra="allow"):
raise AttributeError(f"No device named '{accessor}'") raise AttributeError(f"No device named '{accessor}'")
def export_api(self) -> List[Dict[str, Any]]:
"""Export API-facing device fields."""
return [d.export_api() for d in self.objects]
def networks(self, params: Params) -> List[Dict[str, Any]]: def networks(self, params: Params) -> List[Dict[str, Any]]:
"""Group devices by network.""" """Group devices by network."""
names = {device.network.display_name for device in self.objects} names = {device.network.display_name for device in self.objects}