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

add docs via pydantic field schemas

This commit is contained in:
checktheroads
2020-02-01 16:11:01 -10:00
parent af4a0b0ea7
commit 0ca5bc0ff6
12 changed files with 568 additions and 157 deletions

10
.flake8
View File

@@ -8,14 +8,12 @@ filename=*.py
per-file-ignores= per-file-ignores=
# Disable redefinition warning for exception handlers # Disable redefinition warning for exception handlers
hyperglass/api.py:F811 hyperglass/api.py:F811
# Disable string length warnings so one can actually read the commands
hyperglass/configuration/models/commands.py:E501,C0301
hyperglass/configuration/models/docs.py:E501,C0301
hyperglass/configuration/models/messages.py:E501,C0301
hyperglass/api/models/response.py:E501,C0301
# Disable classmethod warning for validator decorators # Disable classmethod warning for validator decorators
hyperglass/configuration/models/*.py:N805,E0213,R0903
hyperglass/models/*.py:N805,E0213,R0903 hyperglass/models/*.py:N805,E0213,R0903
hyperglass/configuration/models/*.py:N805,E0213,R0903
# Disable string length warnings so one can actually read the commands
hyperglass/configuration/models/*.py:E501,C0301
hyperglass/api/models/response.py:E501,C0301
ignore=W503,C0330,R504,D202 ignore=W503,C0330,R504,D202
select=B, BLK, C, D, E, F, I, II, N, P, PIE, S, R, W select=B, BLK, C, D, E, F, I, II, N, P, PIE, S, R, W
disable-noqa=False disable-noqa=False

View File

@@ -51,7 +51,7 @@ app = FastAPI(
default_response_class=UJSONResponse, default_response_class=UJSONResponse,
docs_url=None, docs_url=None,
redoc_url=None, redoc_url=None,
openapi_url=params.docs.openapi_url, openapi_url=params.docs.openapi_uri,
) )
# Add Event Handlers # Add Event Handlers
@@ -133,7 +133,10 @@ app.add_api_route(
tags=[params.docs.query.title], tags=[params.docs.query.title],
response_class=UJSONResponse, response_class=UJSONResponse,
) )
app.add_api_route(path="/api/docs", endpoint=docs, include_in_schema=False)
if params.docs.enable:
app.add_api_route(path=params.docs.uri, endpoint=docs, include_in_schema=False)
app.mount("/images", StaticFiles(directory=IMAGES_DIR), name="images") app.mount("/images", StaticFiles(directory=IMAGES_DIR), name="images")
app.mount("/", StaticFiles(directory=UI_DIR, html=True), name="ui") app.mount("/", StaticFiles(directory=UI_DIR, html=True), name="ui")

View File

@@ -1,6 +1,11 @@
"""Validation model for Redis cache config.""" """Validation model for Redis cache config."""
# Standard Library Imports
from typing import Union
# Third Party Imports # Third Party Imports
from pydantic import Field
from pydantic import IPvAnyAddress
from pydantic import StrictBool from pydantic import StrictBool
from pydantic import StrictInt from pydantic import StrictInt
from pydantic import StrictStr from pydantic import StrictStr
@@ -12,8 +17,26 @@ from hyperglass.configuration.models._utils import HyperglassModel
class Cache(HyperglassModel): class Cache(HyperglassModel):
"""Validation model for params.cache.""" """Validation model for params.cache."""
host: StrictStr = "localhost" host: Union[IPvAnyAddress, StrictStr] = Field(
port: StrictInt = 6379 "localhost", title="Host", description="Redis server IP address or hostname."
database: StrictInt = 0 )
timeout: StrictInt = 120 port: StrictInt = Field(6379, title="Port", description="Redis server TCP port.")
show_text: StrictBool = True database: StrictInt = Field(
0, title="Database ID", description="Redis server database ID."
)
timeout: StrictInt = Field(
120,
title="Timeout",
description="Time in seconds query output will be kept in the Redis cache.",
)
show_text: StrictBool = Field(
True,
title="Show Text",
description="Show the [`cache`](/fixme) text in the hyperglass UI.",
)
class Config:
"""Pydantic model configuration."""
title = "Cache"
description = "Redis server & cache timeout configuration."

View File

@@ -1,5 +1,6 @@
"""Configuration for API docs feature.""" """Configuration for API docs feature."""
# Third Party Imports # Third Party Imports
from pydantic import Field
from pydantic import StrictBool from pydantic import StrictBool
from pydantic import StrictStr from pydantic import StrictStr
from pydantic import constr from pydantic import constr
@@ -12,23 +13,49 @@ from hyperglass.configuration.models._utils import HyperglassModel
class EndpointConfig(HyperglassModel): class EndpointConfig(HyperglassModel):
"""Validation model for per API endpoint documentation.""" """Validation model for per API endpoint documentation."""
title: StrictStr title: StrictStr = Field(
description: StrictStr ...,
summary: StrictStr title="Endpoint Title",
description="Displayed as the header text above the API endpoint section.",
)
description: StrictStr = Field(
...,
title="Endpoint Description",
description="Displayed inside each API endpoint section.",
)
summary: StrictStr = Field(
...,
title="Endpoint Summary",
description="Displayed beside the API endpoint URI.",
)
class Docs(HyperglassModel): class Docs(HyperglassModel):
"""Validation model for params.docs.""" """Validation model for params.docs."""
enable: StrictBool = True enable: StrictBool = Field(
mode: constr(regex=r"(swagger|redoc)") = "swagger" True, title="Enable", description="Enable or disable API documentation."
uri: AnyUri = "/docs" )
openapi_url: AnyUri = "/openapi.json" mode: constr(regex=r"(swagger|redoc)") = Field(
query: EndpointConfig = { "swagger",
"title": "Submit Query", title="Docs Mode",
"description": "Request a query response per-location.", description="OpenAPI UI library to use for the hyperglass API docs. Currently, the options are [Swagger UI](/fixme) and [Redoc](/fixme).",
"summary": "Query the Looking Glass", )
} uri: AnyUri = Field(
"/api/docs",
title="URI",
description="HTTP URI/path where API documentation can be accessed.",
)
openapi_uri: AnyUri = Field(
"/openapi.json",
title="OpenAPI URI",
description="Path to the automatically generated `openapi.json` file.",
)
query: EndpointConfig = EndpointConfig(
title="Submit Query",
description="Request a query response per-location.",
summary="Query the Looking Glass",
)
devices: EndpointConfig = EndpointConfig( devices: EndpointConfig = EndpointConfig(
title="Devices", title="Devices",
description="List of all devices/locations with associated identifiers, display names, networks, & VRFs.", description="List of all devices/locations with associated identifiers, display names, networks, & VRFs.",
@@ -39,3 +66,23 @@ class Docs(HyperglassModel):
description="List of supported query types.", description="List of supported query types.",
summary="Query Types", summary="Query Types",
) )
class Config:
"""Pydantic model configuration."""
title = "API Docs"
description = "API documentation configuration parameters"
fields = {
"query": {
"title": "Query API Endpoint",
"description": "`/api/query/` API documentation options.",
},
"devices": {
"title": "Devices API Endpoint",
"description": "`/api/devices` API documentation options.",
},
"queries": {
"title": "Queries API Endpoint",
"description": "`/api/devices` API documentation options.",
},
}

View File

@@ -1,6 +1,7 @@
"""Validate error message configuration variables.""" """Validate error message configuration variables."""
# Third Party Imports # Third Party Imports
from pydantic import Field
from pydantic import StrictStr from pydantic import StrictStr
# Project Imports # Project Imports
@@ -10,25 +11,78 @@ from hyperglass.configuration.models._utils import HyperglassModel
class Messages(HyperglassModel): class Messages(HyperglassModel):
"""Validation model for params.messages.""" """Validation model for params.messages."""
no_input: StrictStr = "{field} must be specified." no_input: StrictStr = Field(
acl_denied: StrictStr = "{target} is a member of {denied_network}, which is not allowed." "{field} must be specified.",
acl_not_allowed: StrictStr = "{target} is not allowed." title="No Input",
max_prefix: StrictStr = ( description="Displayed when no a required field is not specified. `{field}` may be used to display the `display_name` of the field that was omitted.",
"Prefix length must be shorter than /{max_length}. {target} is too specific."
) )
requires_ipv6_cidr: StrictStr = ( acl_denied: StrictStr = Field(
"{device_name} requires IPv6 BGP lookups to be in CIDR notation." "{target} is a member of {denied_network}, which is not allowed.",
title="ACL - Denied",
description="Displayed when a query target is explicitly denied by a matched VRF's ACL entry. `{target}` and `{denied_network}` may be used to display the denied query target and the ACL entry that caused it to be denied.",
) )
feature_not_enabled: StrictStr = "{feature} is not enabled for {device_name}." acl_not_allowed: StrictStr = Field(
invalid_input: StrictStr = "{target} is not a valid {query_type} target." "{target} is not allowed.",
invalid_field: StrictStr = "{input} is an invalid {field}." title="ACL - Not Allowed",
general: StrictStr = "Something went wrong." description="Displayed when a query target is implicitly denied by a matched VRF's ACL. `{target}` may be used to display the denied query target.",
directed_cidr: StrictStr = "{query_type} queries can not be in CIDR format." )
request_timeout: StrictStr = "Request timed out." feature_not_enabled: StrictStr = Field(
connection_error: StrictStr = "Error connecting to {device_name}: {error}" "{feature} is not enabled for {device_name}.",
authentication_error: StrictStr = "Authentication error occurred." title="Feature Not Enabled",
noresponse_error: StrictStr = "No response." description="Displayed when a query type is submitted that is not supported or disabled. The hyperglass UI performs validation of supported query types prior to submitting any requests, so this is primarily relevant to the hyperglass API. `{feature}` and `{device_name}` may be used to display the disabled feature and the selected device/location.",
vrf_not_associated: StrictStr = "VRF {vrf_name} is not associated with {device_name}." )
vrf_not_found: StrictStr = "VRF {vrf_name} is not defined." invalid_input: StrictStr = Field(
no_matching_vrfs: StrictStr = "No VRFs in Common" "{target} is not a valid {query_type} target.",
no_output: StrictStr = "No output." title="Invalid Input",
description="Displayed when a query target's value is invalid in relation to the corresponding query type. `{target}` and `{query_type}` maybe used to display the invalid target and corresponding query type.",
)
invalid_field: StrictStr = Field(
"{input} is an invalid {field}.",
title="Invalid Field",
description="Displayed when a query field contains an invalid or unsupported value. `{input}` and `{field}` may be used to display the invalid input value and corresponding field name.",
)
general: StrictStr = Field(
"Something went wrong.",
title="General Error",
description="Displayed when generalized errors occur. Seeing this error message may indicate a bug in hyperglass, as most other errors produced are highly contextual. If you see this in the wild, try enabling [debug mode](/fixme) and review the logs to pinpoint the source of the error.",
)
request_timeout: StrictStr = Field(
"Request timed out.",
title="Request Timeout",
description="Displayed when the [`request_timeout`](/fixme) time expires.",
)
connection_error: StrictStr = Field(
"Error connecting to {device_name}: {error}",
title="Displayed when hyperglass is unable to connect to a configured device. Usually, this indicates a configuration error. `{device_name}` and `{error}` may be used to display the device in question and the specific connection error.",
)
authentication_error: StrictStr = Field(
"Authentication error occurred.",
title="Authentication Error",
description="Displayed when hyperglass is unable to authenticate to a configured device. Usually, this indicates a configuration error.",
)
no_response: StrictStr = Field(
"No response.",
title="No Response",
description="Displayed when hyperglass can connect to a device, but no output able to be read. Seeing this error may indicate a bug in hyperglas or one of its dependencies. If you see this in the wild, try enabling [debug mode](/fixme) and review the logs to pinpoint the source of the error.",
)
vrf_not_associated: StrictStr = Field(
"VRF {vrf_name} is not associated with {device_name}.",
title="VRF Not Associated",
description="Displayed when a query request's VRF field value contains a VRF that is not configured or associated with the corresponding location/device. The hyperglass UI automatically filters out VRFs that are not configured on a selected device, so this error is most likely to appear when using the hyperglass API. `{vrf_name}` and `{device_name}` may be used to display the VRF in question and corresponding device.",
)
vrf_not_found: StrictStr = Field(
"VRF {vrf_name} is not defined.",
title="VRF Not Found",
description="Displayed when a query VRF is not configured on any devices. The hyperglass UI only shows configured VRFs, so this error is most likely to appear when using the hyperglass API. `{vrf_name}` may be used to display the VRF in question.",
)
no_output: StrictStr = Field(
"No output.",
title="No Output",
description="Displayed when hyperglass can connect to a device and execute a query, but the response is empty.",
)
class Config:
"""Pydantic model configuration."""
title = "Messages"
description = "Customize almost all user-facing UI & API messages."

View File

@@ -1,6 +1,7 @@
"""Validate network configuration variables.""" """Validate network configuration variables."""
# Third Party Imports # Third Party Imports
from pydantic import Field
from pydantic import StrictStr from pydantic import StrictStr
# Project Imports # Project Imports
@@ -11,8 +12,16 @@ from hyperglass.configuration.models._utils import clean_name
class Network(HyperglassModel): class Network(HyperglassModel):
"""Validation Model for per-network/asn config in devices.yaml.""" """Validation Model for per-network/asn config in devices.yaml."""
name: StrictStr name: StrictStr = Field(
display_name: StrictStr ...,
title="Network Name",
description="Internal name of the device's primary network.",
)
display_name: StrictStr = Field(
...,
title="Network Display Name",
description="Display name of the device's primary network.",
)
class Networks(HyperglassModel): class Networks(HyperglassModel):

View File

@@ -6,7 +6,7 @@ from typing import Optional
import PIL.Image as PilImage import PIL.Image as PilImage
from pydantic import FilePath from pydantic import FilePath
from pydantic import StrictInt from pydantic import StrictInt
from pydantic import validator from pydantic import root_validator
# Project Imports # Project Imports
from hyperglass.configuration.models._utils import HyperglassModel from hyperglass.configuration.models._utils import HyperglassModel
@@ -19,8 +19,8 @@ class OpenGraph(HyperglassModel):
height: Optional[StrictInt] height: Optional[StrictInt]
image: Optional[FilePath] image: Optional[FilePath]
@validator("image") @root_validator
def validate_image(cls, value, values): def validate_image(cls, values):
"""Set default opengraph image location. """Set default opengraph image location.
Arguments: Arguments:
@@ -29,14 +29,48 @@ class OpenGraph(HyperglassModel):
Returns: Returns:
{Path} -- Opengraph image file path object {Path} -- Opengraph image file path object
""" """
if value is None: supported_extensions = (".jpg", ".jpeg", ".png")
value = ( if (
values["image"].suffix is not None
and values["image"] not in supported_extensions
):
raise ValueError(
"OpenGraph image must be one of {e}".format(
e=", ".join(supported_extensions)
)
)
if values["image"] is None:
image = (
Path(__file__).parent.parent.parent Path(__file__).parent.parent.parent
/ "static/images/hyperglass-opengraph.png" / "static/images/hyperglass-opengraph.png"
) )
with PilImage.open(value) as img: values["image"] = "".join(str(image).split("static")[1::])
with PilImage.open(image) as img:
width, height = img.size width, height = img.size
if values["width"] is None:
values["width"] = width values["width"] = width
if values["height"] is None:
values["height"] = height values["height"] = height
return "".join(str(value).split("static")[1::]) return values
class Config:
"""Pydantic model configuration."""
title = "OpenGraph"
description = "OpenGraph configuration parameters"
fields = {
"width": {
"title": "Width",
"description": "Width of OpenGraph image. If unset, the width will be automatically derived by reading the image file.",
},
"height": {
"title": "Height",
"description": "Height of OpenGraph image. If unset, the height will be automatically derived by reading the image file.",
},
"image": {
"title": "Image File",
"description": "Valid path to a JPG or PNG file to use as the OpenGraph image.",
},
}

View File

@@ -9,6 +9,7 @@ from typing import Optional
from typing import Union from typing import Union
# Third Party Imports # Third Party Imports
from pydantic import Field
from pydantic import FilePath from pydantic import FilePath
from pydantic import IPvAnyAddress from pydantic import IPvAnyAddress
from pydantic import StrictBool from pydantic import StrictBool
@@ -29,13 +30,38 @@ class Params(HyperglassModel):
"""Validation model for all configuration variables.""" """Validation model for all configuration variables."""
# Top Level Params # Top Level Params
debug: StrictBool = False debug: StrictBool = Field(
developer_mode: StrictBool = False False,
primary_asn: Union[StrictInt, StrictStr] = "65001" title="Debug",
org_name: StrictStr = "Beloved Hyperglass User" description="Enable debug mode. Warning: this will generate a *lot* of log output.",
site_title: StrictStr = "hyperglass" )
site_description: StrictStr = "{org_name} Network Looking Glass" developer_mode: StrictBool = Field(
site_keywords: List[StrictStr] = [ False,
title="Developer Mode",
description='Enable developer mode. If enabled, the hyperglass backend (Python) and frontend (React/Javascript) applications are "unlinked", so that React tools can be used for front end development. A `<Debugger />` convenience component is also displayed in the UI for easier UI development.',
)
primary_asn: Union[StrictInt, StrictStr] = Field(
"65001",
title="Primary ASN",
description="Your network's primary ASN. This field is used to set some useful defaults such as the subtitle and PeeringDB URL.",
)
org_name: StrictStr = Field(
"Beloved Hyperglass User",
title="Organization Name",
description="Your organization's name. This field is used in the UI & API documentation to set fields such as `<meta/>` HTML tags for SEO and the terms & conditions footer component.",
)
site_title: StrictStr = Field(
"hyperglass",
title="Site Title",
description="The name of your hyperglass site. This field is used in the UI & API documentation to set fields such as the `<title/>` HTML tag, and the terms & conditions footer component.",
)
site_description: StrictStr = Field(
"{org_name} Network Looking Glass",
title="Site Description",
description='A short description of your hyperglass site. This field is used in th UI & API documentation to set the `<meta name="description"/>` tag. `{org_name}` may be used to insert the value of the `org_name` field.',
)
site_keywords: List[StrictStr] = Field(
[
"hyperglass", "hyperglass",
"looking glass", "looking glass",
"lg", "lg",
@@ -50,13 +76,27 @@ class Params(HyperglassModel):
"routing", "routing",
"network", "network",
"isp", "isp",
] ],
requires_ipv6_cidr: List[StrictStr] = ["cisco_ios", "cisco_nxos"] title="Site Keywords",
request_timeout: StrictInt = 30 description='Keywords pertaining to your hyperglass site. This field is used to generate `<meta name="keywords"/>` HTML tags, which helps tremendously with SEO.',
)
request_timeout: StrictInt = Field(
30,
title="Request Timeout",
description="Global timeout in seconds for all requests. The frontend application (UI) uses this field's exact value when submitting queries. The backend application uses this field's value, minus one second, for its own timeout handling. This is to ensure a contextual timeout error is presented to the end user in the event of a backend application timeout.",
)
listen_address: Optional[Union[IPvAnyAddress, StrictStr]] listen_address: Optional[Union[IPvAnyAddress, StrictStr]]
listen_port: StrictInt = 8001 listen_port: StrictInt = Field(
8001,
title="Listen Port",
description="Local TCP port the hyperglass application listens on to serve web traffic.",
)
log_file: Optional[FilePath] log_file: Optional[FilePath]
cors_origins: List[StrictStr] = [] cors_origins: List[StrictStr] = Field(
[],
title="Cross-Origin Resource Sharing",
description="Allowed CORS hosts. By default, no CORS hosts are allowed.",
)
# Sub Level Params # Sub Level Params
cache: Cache = Cache() cache: Cache = Cache()
@@ -65,6 +105,20 @@ class Params(HyperglassModel):
queries: Queries = Queries() queries: Queries = Queries()
web: Web = Web() web: Web = Web()
class Config:
"""Pydantic model configuration."""
fields = {
"listen_address": {
"title": "Listen Address",
"description": "Local IP Address or hostname the hyperglass application listens on to serve web traffic.",
},
"log_file": {
"title": "Log File",
"description": "Path to a log file to which hyperglass can write logs. If none is set, hyperglass will write logs to a file located at `/tmp/`, with a uniquely generated name for each time hyperglass is started.",
},
}
@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):
"""Set default listen_address based on debug mode. """Set default listen_address based on debug mode.

View File

@@ -1,8 +1,8 @@
"""Validate query configuration parameters.""" """Validate query configuration parameters."""
# Third Party Imports # Third Party Imports
from pydantic import Field
from pydantic import StrictBool from pydantic import StrictBool
from pydantic import StrictInt
from pydantic import StrictStr from pydantic import StrictStr
from pydantic import constr from pydantic import constr
@@ -11,73 +11,136 @@ from hyperglass.configuration.models._utils import HyperglassModel
from hyperglass.constants import SUPPORTED_QUERY_TYPES from hyperglass.constants import SUPPORTED_QUERY_TYPES
class BgpCommunityPattern(HyperglassModel):
"""Validation model for bgp_community regex patterns."""
decimal: StrictStr = Field(
r"^[0-9]{1,10}$",
title="Decimal Community",
description="Regular expression pattern for validating decimal type BGP Community strings.",
)
extended_as: StrictStr = Field(
r"^([0-9]{0,5})\:([0-9]{1,5})$",
title="Extended AS Community",
description="Regular expression pattern for validating extended AS type BGP Community strings, e.g. `65000:1`",
)
large: StrictStr = Field(
r"^([0-9]{1,10})\:([0-9]{1,10})\:[0-9]{1,10}$",
title="Large Community",
description="Regular expression pattern for validating large community strings, e.g. `65000:65001:65002`",
)
class Config:
"""Pydantic model configuration."""
title = "Pattern"
description = (
"Regular expression patterns used to validate BGP Community queries."
)
class BgpAsPathPattern(HyperglassModel):
"""Validation model for bgp_aspath regex patterns."""
mode: constr(regex=r"asplain|asdot") = Field(
"asplain",
title="AS Path Mode",
description="Set ASN display mode. This field is dependent on how your network devices are configured.",
)
asplain: StrictStr = Field(
r"^(\^|^\_)(\d+\_|\d+\$|\d+\(\_\.\+\_\))+$",
title="AS Plain",
description="Regular expression pattern for validating [AS Plain](/fixme) type BGP AS Path queries.",
)
asdot: StrictStr = Field(
r"^(\^|^\_)((\d+\.\d+)\_|(\d+\.\d+)\$|(\d+\.\d+)\(\_\.\+\_\))+$",
title="AS Dot",
description="Regular expression pattern for validating [AS Dot](/fixme) type BGP AS Path queries.",
)
class Config:
"""Pydantic model configuration."""
title = "Pattern"
description = (
"Regular expression patterns used to validate BGP AS Path queries."
)
class BgpCommunity(HyperglassModel): class BgpCommunity(HyperglassModel):
"""Validation model for bgp_community configuration.""" """Validation model for bgp_community configuration."""
class Pattern(HyperglassModel): enable: StrictBool = Field(
"""Validation model for bgp_community regex patterns.""" True,
title="Enable",
decimal: StrictStr = r"^[0-9]{1,10}$" description="Enable or disable the BGP Community query type.",
extended_as: StrictStr = r"^([0-9]{0,5})\:([0-9]{1,5})$" )
large: StrictStr = r"^([0-9]{1,10})\:([0-9]{1,10})\:[0-9]{1,10}$" display_name: StrictStr = Field(
"BGP Community",
enable: StrictBool = True title="Display Name",
display_name: StrictStr = "BGP Community" description="Text displayed for the BGP Community query type in the hyperglas UI.",
pattern: Pattern = Pattern() )
pattern: BgpCommunityPattern = BgpCommunityPattern()
class BgpRoute(HyperglassModel): class BgpRoute(HyperglassModel):
"""Validation model for bgp_route configuration.""" """Validation model for bgp_route configuration."""
enable: StrictBool = True enable: StrictBool = Field(
display_name: StrictStr = "BGP Route" True, title="Enable", description="Enable or disable the BGP Route query type."
)
display_name: StrictStr = Field(
"BGP Route",
title="Display Name",
description="Text displayed for the BGP Route query type in the hyperglas UI.",
)
class BgpAsPath(HyperglassModel): class BgpAsPath(HyperglassModel):
"""Validation model for bgp_aspath configuration.""" """Validation model for bgp_aspath configuration."""
enable: StrictBool = True enable: StrictBool = Field(
display_name: StrictStr = "BGP AS Path" True,
title="Enable",
class Pattern(HyperglassModel): description="Enable or disable the BGP AS Path query type.",
"""Validation model for bgp_aspath regex patterns."""
mode: constr(regex="asplain|asdot") = "asplain"
asplain: StrictStr = r"^(\^|^\_)(\d+\_|\d+\$|\d+\(\_\.\+\_\))+$"
asdot: StrictStr = (
r"^(\^|^\_)((\d+\.\d+)\_|(\d+\.\d+)\$|(\d+\.\d+)\(\_\.\+\_\))+$"
) )
display_name: StrictStr = Field(
pattern: Pattern = Pattern() "BGP AS Path",
title="Display Name",
description="Text displayed for the BGP AS Path query type in the hyperglas UI.",
)
pattern: BgpAsPathPattern = BgpAsPathPattern()
class Ping(HyperglassModel): class Ping(HyperglassModel):
"""Validation model for ping configuration.""" """Validation model for ping configuration."""
enable: StrictBool = True enable: StrictBool = Field(
display_name: StrictStr = "Ping" True, title="Enable", description="Enable or disable the Ping query type."
)
display_name: StrictStr = Field(
"Ping",
title="Display Name",
description="Text displayed for the Ping query type in the hyperglas UI.",
)
class Traceroute(HyperglassModel): class Traceroute(HyperglassModel):
"""Validation model for traceroute configuration.""" """Validation model for traceroute configuration."""
enable: StrictBool = True enable: StrictBool = Field(
display_name: StrictStr = "Traceroute" True, title="Enable", description="Enable or disable the Traceroute query type."
)
display_name: StrictStr = Field(
"Traceroute",
title="Display Name",
description="Text displayed for the Traceroute query type in the hyperglas UI.",
)
class Queries(HyperglassModel): class Queries(HyperglassModel):
"""Validation model for all query types.""" """Validation model for all query types."""
class MaxPrefix(HyperglassModel):
"""Validation model for params.features.max_prefix."""
enable: StrictBool = False
ipv4: StrictInt = 24
ipv6: StrictInt = 64
message: StrictStr = (
"Prefix length must be smaller than /{m}. <b>{i}</b> is too specific."
)
@property @property
def map(self): def map(self):
"""Return a dict of all query display names, internal names, and enable state. """Return a dict of all query display names, internal names, and enable state.
@@ -119,4 +182,31 @@ class Queries(HyperglassModel):
bgp_aspath: BgpAsPath = BgpAsPath() bgp_aspath: BgpAsPath = BgpAsPath()
ping: Ping = Ping() ping: Ping = Ping()
traceroute: Traceroute = Traceroute() traceroute: Traceroute = Traceroute()
max_prefix: MaxPrefix = MaxPrefix()
class Config:
"""Pydantic model configuration."""
title = "Queries"
description = "Enable, disable, or configure query types."
fields = {
"bgp_route": {
"title": "BGP Route",
"description": "Enable, disable, or configure the BGP Route query type.",
},
"bgp_community": {
"title": "BGP Community",
"description": "Enable, disable, or configure the BGP Community query type.",
},
"bgp_aspath": {
"title": "BGP AS Path",
"description": "Enable, disable, or configure the BGP AS Path query type.",
},
"ping": {
"title": "Ping",
"description": "Enable, disable, or configure the Ping query type.",
},
"traceroute": {
"title": "Traceroute",
"description": "Enable, disable, or configure the Traceroute query type.",
},
}

View File

@@ -1,6 +1,10 @@
"""Validate SSL configuration variables.""" """Validate SSL configuration variables."""
# Standard Library Imports
from typing import Optional
# Third Party Imports # Third Party Imports
from pydantic import Field
from pydantic import FilePath from pydantic import FilePath
from pydantic import StrictBool from pydantic import StrictBool
@@ -11,5 +15,21 @@ from hyperglass.configuration.models._utils import HyperglassModel
class Ssl(HyperglassModel): class Ssl(HyperglassModel):
"""Validate SSL config parameters.""" """Validate SSL config parameters."""
enable: StrictBool = True enable: StrictBool = Field(
cert: FilePath True,
title="Enable SSL",
description="If enabled, hyperglass will use HTTPS to connect to the configured device running [hyperglass-agent](/fixme). If enabled, a certificate file must be specified (hyperglass does not support connecting to a device over an unverified SSL session.)",
)
cert: Optional[FilePath]
class Config:
"""Pydantic model configuration."""
title = "SSL"
description = "SSL configuration for devices running hyperglass-agent."
fields = {
"cert": {
"title": "Certificate",
"description": "Valid path to an SSL certificate. This certificate must be the public key used to serve the hyperglass-agent API on the device running hyperglass-agent.",
}
}

View File

@@ -9,6 +9,7 @@ from typing import List
from typing import Optional from typing import Optional
# Third Party Imports # Third Party Imports
from pydantic import Field
from pydantic import FilePath from pydantic import FilePath
from pydantic import StrictBool from pydantic import StrictBool
from pydantic import StrictStr from pydantic import StrictStr
@@ -25,10 +26,26 @@ from hyperglass.configuration.models._utils import HyperglassModelExtra
class AccessList4(HyperglassModel): class AccessList4(HyperglassModel):
"""Validation model for IPv4 access-lists.""" """Validation model for IPv4 access-lists."""
network: IPv4Network = "0.0.0.0/0" network: IPv4Network = Field(
action: constr(regex="permit|deny") = "permit" "0.0.0.0/0",
ge: conint(ge=0, le=32) = 0 title="Network",
le: conint(ge=0, le=32) = 32 description="IPv4 Network/Prefix that should be permitted or denied. ",
)
action: constr(regex=r"permit|deny") = Field(
"permit",
title="Action",
description="Permit or deny any networks contained within the prefix.",
)
ge: conint(ge=0, le=32) = Field(
0,
title="Greater Than or Equal To",
description="Similar to `ge` in a Cisco prefix-list, the `ge` field defines the **bottom** threshold for prefix size. For example, a value of `24` would result in a query for `192.0.2.0/23` being denied, but a query for `192.0.2.0/32` would be permitted. If this field is set to a value smaller than the `network` field's prefix length, this field's value will be overwritten to the prefix length of the prefix in the `network` field.",
)
le: conint(ge=0, le=32) = Field(
32,
title="Less Than or Equal To",
description="Similar to `le` in a Cisco prefix-list, the `le` field defines the **top** threshold for prefix size. For example, a value of `24` would result in a query for `192.0.2.0/23` being permitted, but a query for `192.0.2.0/32` would be denied.",
)
@validator("ge") @validator("ge")
def validate_model(cls, value, values): def validate_model(cls, value, values):
@@ -50,10 +67,31 @@ class AccessList4(HyperglassModel):
class AccessList6(HyperglassModel): class AccessList6(HyperglassModel):
"""Validation model for IPv6 access-lists.""" """Validation model for IPv6 access-lists."""
network: IPv6Network = "::/0" network: IPv6Network = Field(
action: constr(regex=r"permit|deny") = "permit" "::/0",
ge: conint(ge=0, le=128) = 0 title="Network",
le: conint(ge=0, le=128) = 128 description="IPv6 Network/Prefix that should be permitted or denied. ",
)
action: constr(regex=r"permit|deny") = Field(
"permit",
title="Action",
description="Permit or deny any networks contained within the prefix.",
# regex="permit|deny",
)
ge: conint(ge=0, le=128) = Field(
0,
title="Greater Than or Equal To",
description="Similar to `ge` in a Cisco prefix-list, the `ge` field defines the **bottom** threshold for prefix size. For example, a value of `64` would result in a query for `2001:db8::/48` being denied, but a query for `2001:db8::1/128` would be permitted. If this field is set to a value smaller than the `network` field's prefix length, this field's value will be overwritten to the prefix length of the prefix in the `network` field.",
# ge=0,
# le=128,
)
le: conint(ge=0, le=128) = Field(
128,
title="Less Than or Equal To",
description="Similar to `le` in a Cisco prefix-list, the `le` field defines the **top** threshold for prefix size. For example, a value of `64` would result in a query for `2001:db8::/48` being permitted, but a query for `2001:db8::1/128` would be denied.",
# ge=0,
# le=128,
)
@validator("ge") @validator("ge")
def validate_model(cls, value, values): def validate_model(cls, value, values):
@@ -77,6 +115,12 @@ class InfoConfigParams(HyperglassModelExtra):
title: Optional[StrictStr] title: Optional[StrictStr]
class Config:
"""Pydantic model configuration."""
title = "Help Parameters"
description = "Set dynamic or reusable values which may be used in the help/information content. Params my be access by using Python string formatting syntax, e.g. `{param_name}`. Any arbitrary values may be added."
class InfoConfig(HyperglassModel): class InfoConfig(HyperglassModel):
"""Validation model for help configuration.""" """Validation model for help configuration."""
@@ -85,6 +129,20 @@ class InfoConfig(HyperglassModel):
file: Optional[FilePath] file: Optional[FilePath]
params: InfoConfigParams = InfoConfigParams() params: InfoConfigParams = InfoConfigParams()
class Config:
"""Pydantic model configuration."""
fields = {
"enable": {
"title": "Enable",
"description": "Enable or disable the display of help/information content for this query type.",
},
"file": {
"title": "File Name",
"description": "Path to a valid text or Markdown file containing custom content.",
},
}
class Info(HyperglassModel): class Info(HyperglassModel):
"""Validation model for per-VRF, per-Command help.""" """Validation model for per-VRF, per-Command help."""
@@ -95,6 +153,34 @@ class Info(HyperglassModel):
ping: InfoConfig = InfoConfig() ping: InfoConfig = InfoConfig()
traceroute: InfoConfig = InfoConfig() traceroute: InfoConfig = InfoConfig()
class Config:
"""Pydantic model configuration."""
title = "VRF Information"
description = "Per-VRF help & information content."
fields = {
"bgp_aspath": {
"title": "BGP AS Path",
"description": "Show information about bgp_aspath queries when selected.",
},
"bgp_community": {
"title": "BGP Community",
"description": "Show information about bgp_community queries when selected.",
},
"bgp_route": {
"title": "BGP Route",
"description": "Show information about bgp_route queries when selected.",
},
"ping": {
"title": "Ping",
"description": "Show information about ping queries when selected.",
},
"traceroute": {
"title": "Traceroute",
"description": "Show information about traceroute queries when selected.",
},
}
class DeviceVrf4(HyperglassModelExtra): class DeviceVrf4(HyperglassModelExtra):
"""Validation model for IPv4 AFI definitions.""" """Validation model for IPv4 AFI definitions."""
@@ -186,25 +272,18 @@ class Vrf(HyperglassModel):
result = self.name == other.name result = self.name == other.name
return result return result
class Config:
"""Pydantic model configuration."""
class DefaultVrf(HyperglassModel): title = "VRF"
"""Validation model for default routing table VRF.""" description = "Per-VRF configuration."
fields = {
name: constr(regex="default") = "default" "name": {
display_name: StrictStr = "Global" "title": "Name",
info: Info = Info() "description": "VRF name as configured on the router/device.",
},
class DefaultVrf4(HyperglassModel): "display_name": {
"""Validation model for IPv4 default routing table VRF definition.""" "title": "Display Name",
"description": "Display name of VRF for use in the hyperglass UI. If none is specified, hyperglass will attempt to generate one.",
source_address: IPv4Address },
access_list: List[AccessList4] = [AccessList4()] }
class DefaultVrf6(HyperglassModel):
"""Validation model for IPv6 default routing table VRF definition."""
source_address: IPv6Address
access_list: List[AccessList6] = [AccessList6()]
ipv4: Optional[DefaultVrf4]
ipv6: Optional[DefaultVrf6]

View File

@@ -169,7 +169,7 @@ class Connect:
params.messages.connection_error, params.messages.connection_error,
device_name=self.device.display_name, device_name=self.device.display_name,
proxy=None, proxy=None,
error=params.messages.noresponse_error, error=params.messages.no_response,
) )
signal.alarm(0) signal.alarm(0)
return response return response
@@ -243,7 +243,7 @@ class Connect:
params.messages.connection_error, params.messages.connection_error,
device_name=self.device.display_name, device_name=self.device.display_name,
proxy=None, proxy=None,
error=params.messages.noresponse_error, error=params.messages.no_response,
) )
signal.alarm(0) signal.alarm(0)
return response return response
@@ -337,7 +337,7 @@ class Connect:
raise RestError( raise RestError(
params.messages.connection_error, params.messages.connection_error,
device_name=self.device.display_name, device_name=self.device.display_name,
error=params.messages.noresponse_error, error=params.messages.no_response,
) )
return response return response