diff --git a/hyperglass/api/__init__.py b/hyperglass/api/__init__.py index 00410a7..1a4a5c9 100644 --- a/hyperglass/api/__init__.py +++ b/hyperglass/api/__init__.py @@ -18,21 +18,12 @@ from fastapi.middleware.gzip import GZipMiddleware # Project from hyperglass.log import log 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.api.events import on_startup, on_shutdown -from hyperglass.api.routes import ( - docs, - info, - query, - queries, - routers, - ui_props, - communities, - import_certificate, -) +from hyperglass.api.routes import docs, info, query, router, queries, routers, ui_props 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 ( app_handler, http_handler, @@ -211,12 +202,14 @@ app.add_api_route( ) app.add_api_route( - path="/api/communities", - endpoint=communities, + path="/api/devices/{id}", + endpoint=router, methods=["GET"], - response_model=List[CommunityResponse], - summary=params.docs.communities.summary, - tags=[params.docs.communities.title], + response_model=RoutersResponse, + response_class=JSONResponse, + summary=params.docs.devices.summary, + description=params.docs.devices.description, + tags=[params.docs.devices.title], ) app.add_api_route( @@ -255,15 +248,6 @@ app.add_api_route( 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: app.add_api_route(path=params.docs.uri, endpoint=docs, include_in_schema=False) diff --git a/hyperglass/api/routes.py b/hyperglass/api/routes.py index cd2ba9a..f36a6a0 100644 --- a/hyperglass/api/routes.py +++ b/hyperglass/api/routes.py @@ -14,12 +14,11 @@ from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html # Project from hyperglass.log import log from hyperglass.cache import AsyncCache -from hyperglass.encode import jwt_decode 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.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.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): - """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 - """ + """If webhooks are enabled, get request info and send a webhook.""" try: if params.logging.http is not None: 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(): """Serve custom docs.""" if params.docs.enable: @@ -226,27 +176,14 @@ async def docs(): 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(): """Serve list of configured routers and attributes.""" - return [ - 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] + return devices.export_api() async def queries(): @@ -260,7 +197,7 @@ async def info(): "name": params.site_title, "organization": params.org_name, "primary_asn": int(params.primary_asn), - "version": f"hyperglass {__version__}", + "version": __version__, } diff --git a/hyperglass/models/api/response.py b/hyperglass/models/api/response.py index 3b83a2c..82ffd19 100644 --- a/hyperglass/models/api/response.py +++ b/hyperglass/models/api/response.py @@ -178,16 +178,24 @@ class Network(BaseModel): class RoutersResponse(BaseModel): """Response model for /api/devices list items.""" + id: StrictStr name: StrictStr - network: Network - vrfs: List[Vrf] + network: StrictStr class Config: """Pydantic model configuration.""" title = "Device" - description = "Per-device attributes" - schema_extra = {"examples": [{"name": "router01-nyc01", "location": "nyc01"}]} + description = "Device attributes" + schema_extra = { + "examples": [ + { + "id": "nyc_router_1", + "name": "NYC Router 1", + "network": "New York City, NY", + } + ] + } class CommunityResponse(BaseModel): diff --git a/hyperglass/models/config/devices.py b/hyperglass/models/config/devices.py index 36ff07a..612094f 100644 --- a/hyperglass/models/config/devices.py +++ b/hyperglass/models/config/devices.py @@ -103,6 +103,14 @@ class Device(HyperglassModel, extra="allow"): 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 def directive_commands(self) -> List[str]: """Get all commands associated with the device.""" @@ -278,6 +286,10 @@ class Devices(HyperglassModel, extra="allow"): 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]]: """Group devices by network.""" names = {device.network.display_name for device in self.objects}