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=
# Disable redefinition warning for exception handlers
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
hyperglass/configuration/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
select=B, BLK, C, D, E, F, I, II, N, P, PIE, S, R, W
disable-noqa=False

View File

@@ -51,7 +51,7 @@ app = FastAPI(
default_response_class=UJSONResponse,
docs_url=None,
redoc_url=None,
openapi_url=params.docs.openapi_url,
openapi_url=params.docs.openapi_uri,
)
# Add Event Handlers
@@ -133,7 +133,10 @@ app.add_api_route(
tags=[params.docs.query.title],
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("/", StaticFiles(directory=UI_DIR, html=True), name="ui")

View File

@@ -1,6 +1,11 @@
"""Validation model for Redis cache config."""
# Standard Library Imports
from typing import Union
# Third Party Imports
from pydantic import Field
from pydantic import IPvAnyAddress
from pydantic import StrictBool
from pydantic import StrictInt
from pydantic import StrictStr
@@ -12,8 +17,26 @@ from hyperglass.configuration.models._utils import HyperglassModel
class Cache(HyperglassModel):
"""Validation model for params.cache."""
host: StrictStr = "localhost"
port: StrictInt = 6379
database: StrictInt = 0
timeout: StrictInt = 120
show_text: StrictBool = True
host: Union[IPvAnyAddress, StrictStr] = Field(
"localhost", title="Host", description="Redis server IP address or hostname."
)
port: StrictInt = Field(6379, title="Port", description="Redis server TCP port.")
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."""
# Third Party Imports
from pydantic import Field
from pydantic import StrictBool
from pydantic import StrictStr
from pydantic import constr
@@ -12,23 +13,49 @@ from hyperglass.configuration.models._utils import HyperglassModel
class EndpointConfig(HyperglassModel):
"""Validation model for per API endpoint documentation."""
title: StrictStr
description: StrictStr
summary: StrictStr
title: StrictStr = Field(
...,
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):
"""Validation model for params.docs."""
enable: StrictBool = True
mode: constr(regex=r"(swagger|redoc)") = "swagger"
uri: AnyUri = "/docs"
openapi_url: AnyUri = "/openapi.json"
query: EndpointConfig = {
"title": "Submit Query",
"description": "Request a query response per-location.",
"summary": "Query the Looking Glass",
}
enable: StrictBool = Field(
True, title="Enable", description="Enable or disable API documentation."
)
mode: constr(regex=r"(swagger|redoc)") = Field(
"swagger",
title="Docs Mode",
description="OpenAPI UI library to use for the hyperglass API docs. Currently, the options are [Swagger UI](/fixme) and [Redoc](/fixme).",
)
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(
title="Devices",
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.",
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."""
# Third Party Imports
from pydantic import Field
from pydantic import StrictStr
# Project Imports
@@ -10,25 +11,78 @@ from hyperglass.configuration.models._utils import HyperglassModel
class Messages(HyperglassModel):
"""Validation model for params.messages."""
no_input: StrictStr = "{field} must be specified."
acl_denied: StrictStr = "{target} is a member of {denied_network}, which is not allowed."
acl_not_allowed: StrictStr = "{target} is not allowed."
max_prefix: StrictStr = (
"Prefix length must be shorter than /{max_length}. {target} is too specific."
no_input: StrictStr = Field(
"{field} must be specified.",
title="No Input",
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.",
)
requires_ipv6_cidr: StrictStr = (
"{device_name} requires IPv6 BGP lookups to be in CIDR notation."
acl_denied: StrictStr = Field(
"{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}."
invalid_input: StrictStr = "{target} is not a valid {query_type} target."
invalid_field: StrictStr = "{input} is an invalid {field}."
general: StrictStr = "Something went wrong."
directed_cidr: StrictStr = "{query_type} queries can not be in CIDR format."
request_timeout: StrictStr = "Request timed out."
connection_error: StrictStr = "Error connecting to {device_name}: {error}"
authentication_error: StrictStr = "Authentication error occurred."
noresponse_error: StrictStr = "No response."
vrf_not_associated: StrictStr = "VRF {vrf_name} is not associated with {device_name}."
vrf_not_found: StrictStr = "VRF {vrf_name} is not defined."
no_matching_vrfs: StrictStr = "No VRFs in Common"
no_output: StrictStr = "No output."
acl_not_allowed: StrictStr = Field(
"{target} is not allowed.",
title="ACL - Not Allowed",
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.",
)
feature_not_enabled: StrictStr = Field(
"{feature} is not enabled for {device_name}.",
title="Feature Not Enabled",
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.",
)
invalid_input: StrictStr = Field(
"{target} is not a valid {query_type} target.",
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."""
# Third Party Imports
from pydantic import Field
from pydantic import StrictStr
# Project Imports
@@ -11,8 +12,16 @@ from hyperglass.configuration.models._utils import clean_name
class Network(HyperglassModel):
"""Validation Model for per-network/asn config in devices.yaml."""
name: StrictStr
display_name: StrictStr
name: StrictStr = Field(
...,
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):

View File

@@ -6,7 +6,7 @@ from typing import Optional
import PIL.Image as PilImage
from pydantic import FilePath
from pydantic import StrictInt
from pydantic import validator
from pydantic import root_validator
# Project Imports
from hyperglass.configuration.models._utils import HyperglassModel
@@ -19,8 +19,8 @@ class OpenGraph(HyperglassModel):
height: Optional[StrictInt]
image: Optional[FilePath]
@validator("image")
def validate_image(cls, value, values):
@root_validator
def validate_image(cls, values):
"""Set default opengraph image location.
Arguments:
@@ -29,14 +29,48 @@ class OpenGraph(HyperglassModel):
Returns:
{Path} -- Opengraph image file path object
"""
if value is None:
value = (
supported_extensions = (".jpg", ".jpeg", ".png")
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
/ "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
if values["width"] is None:
values["width"] = width
if values["height"] is None:
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
# Third Party Imports
from pydantic import Field
from pydantic import FilePath
from pydantic import IPvAnyAddress
from pydantic import StrictBool
@@ -29,13 +30,38 @@ class Params(HyperglassModel):
"""Validation model for all configuration variables."""
# Top Level Params
debug: StrictBool = False
developer_mode: StrictBool = False
primary_asn: Union[StrictInt, StrictStr] = "65001"
org_name: StrictStr = "Beloved Hyperglass User"
site_title: StrictStr = "hyperglass"
site_description: StrictStr = "{org_name} Network Looking Glass"
site_keywords: List[StrictStr] = [
debug: StrictBool = Field(
False,
title="Debug",
description="Enable debug mode. Warning: this will generate a *lot* of log output.",
)
developer_mode: StrictBool = Field(
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",
"looking glass",
"lg",
@@ -50,13 +76,27 @@ class Params(HyperglassModel):
"routing",
"network",
"isp",
]
requires_ipv6_cidr: List[StrictStr] = ["cisco_ios", "cisco_nxos"]
request_timeout: StrictInt = 30
],
title="Site Keywords",
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_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]
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
cache: Cache = Cache()
@@ -65,6 +105,20 @@ class Params(HyperglassModel):
queries: Queries = Queries()
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)
def validate_listen_address(cls, value, values):
"""Set default listen_address based on debug mode.

View File

@@ -1,8 +1,8 @@
"""Validate query configuration parameters."""
# Third Party Imports
from pydantic import Field
from pydantic import StrictBool
from pydantic import StrictInt
from pydantic import StrictStr
from pydantic import constr
@@ -11,73 +11,136 @@ from hyperglass.configuration.models._utils import HyperglassModel
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):
"""Validation model for bgp_community configuration."""
class Pattern(HyperglassModel):
"""Validation model for bgp_community regex patterns."""
decimal: StrictStr = r"^[0-9]{1,10}$"
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}$"
enable: StrictBool = True
display_name: StrictStr = "BGP Community"
pattern: Pattern = Pattern()
enable: StrictBool = Field(
True,
title="Enable",
description="Enable or disable the BGP Community query type.",
)
display_name: StrictStr = Field(
"BGP Community",
title="Display Name",
description="Text displayed for the BGP Community query type in the hyperglas UI.",
)
pattern: BgpCommunityPattern = BgpCommunityPattern()
class BgpRoute(HyperglassModel):
"""Validation model for bgp_route configuration."""
enable: StrictBool = True
display_name: StrictStr = "BGP Route"
enable: StrictBool = Field(
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):
"""Validation model for bgp_aspath configuration."""
enable: StrictBool = True
display_name: StrictStr = "BGP AS Path"
class Pattern(HyperglassModel):
"""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+)\(\_\.\+\_\))+$"
enable: StrictBool = Field(
True,
title="Enable",
description="Enable or disable the BGP AS Path query type.",
)
pattern: Pattern = Pattern()
display_name: StrictStr = Field(
"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):
"""Validation model for ping configuration."""
enable: StrictBool = True
display_name: StrictStr = "Ping"
enable: StrictBool = Field(
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):
"""Validation model for traceroute configuration."""
enable: StrictBool = True
display_name: StrictStr = "Traceroute"
enable: StrictBool = Field(
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):
"""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
def map(self):
"""Return a dict of all query display names, internal names, and enable state.
@@ -119,4 +182,31 @@ class Queries(HyperglassModel):
bgp_aspath: BgpAsPath = BgpAsPath()
ping: Ping = Ping()
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."""
# Standard Library Imports
from typing import Optional
# Third Party Imports
from pydantic import Field
from pydantic import FilePath
from pydantic import StrictBool
@@ -11,5 +15,21 @@ from hyperglass.configuration.models._utils import HyperglassModel
class Ssl(HyperglassModel):
"""Validate SSL config parameters."""
enable: StrictBool = True
cert: FilePath
enable: StrictBool = Field(
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
# Third Party Imports
from pydantic import Field
from pydantic import FilePath
from pydantic import StrictBool
from pydantic import StrictStr
@@ -25,10 +26,26 @@ from hyperglass.configuration.models._utils import HyperglassModelExtra
class AccessList4(HyperglassModel):
"""Validation model for IPv4 access-lists."""
network: IPv4Network = "0.0.0.0/0"
action: constr(regex="permit|deny") = "permit"
ge: conint(ge=0, le=32) = 0
le: conint(ge=0, le=32) = 32
network: IPv4Network = Field(
"0.0.0.0/0",
title="Network",
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")
def validate_model(cls, value, values):
@@ -50,10 +67,31 @@ class AccessList4(HyperglassModel):
class AccessList6(HyperglassModel):
"""Validation model for IPv6 access-lists."""
network: IPv6Network = "::/0"
action: constr(regex=r"permit|deny") = "permit"
ge: conint(ge=0, le=128) = 0
le: conint(ge=0, le=128) = 128
network: IPv6Network = Field(
"::/0",
title="Network",
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")
def validate_model(cls, value, values):
@@ -77,6 +115,12 @@ class InfoConfigParams(HyperglassModelExtra):
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):
"""Validation model for help configuration."""
@@ -85,6 +129,20 @@ class InfoConfig(HyperglassModel):
file: Optional[FilePath]
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):
"""Validation model for per-VRF, per-Command help."""
@@ -95,6 +153,34 @@ class Info(HyperglassModel):
ping: 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):
"""Validation model for IPv4 AFI definitions."""
@@ -186,25 +272,18 @@ class Vrf(HyperglassModel):
result = self.name == other.name
return result
class Config:
"""Pydantic model configuration."""
class DefaultVrf(HyperglassModel):
"""Validation model for default routing table VRF."""
name: constr(regex="default") = "default"
display_name: StrictStr = "Global"
info: Info = Info()
class DefaultVrf4(HyperglassModel):
"""Validation model for IPv4 default routing table VRF definition."""
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]
title = "VRF"
description = "Per-VRF configuration."
fields = {
"name": {
"title": "Name",
"description": "VRF name as configured on the router/device.",
},
"display_name": {
"title": "Display Name",
"description": "Display name of VRF for use in the hyperglass UI. If none is specified, hyperglass will attempt to generate one.",
},
}

View File

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