mirror of
https://github.com/checktheroads/hyperglass
synced 2024-05-11 05:55:08 +00:00
refactor logzero → loguru; docstrings
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
"""
|
||||
"""Validate, construct, execute queries.
|
||||
|
||||
Constructs SSH commands or API call parameters based on front end
|
||||
input, executes the commands/calls, returns the output to front end.
|
||||
"""
|
||||
|
@@ -1,29 +1,25 @@
|
||||
"""
|
||||
"""Construct SSH command/API parameters from validated query data.
|
||||
|
||||
Accepts filtered & validated input from execute.py, constructs SSH
|
||||
command for Netmiko library or API call parameters for supported
|
||||
hyperglass API modules.
|
||||
"""
|
||||
|
||||
# Standard Library Imports
|
||||
import ipaddress
|
||||
import json
|
||||
import operator
|
||||
import re
|
||||
|
||||
# Third Party Imports
|
||||
from logzero import logger as log
|
||||
|
||||
# Project Imports
|
||||
from hyperglass.configuration import commands
|
||||
from hyperglass.configuration import logzero_config # NOQA: F401
|
||||
from hyperglass.constants import target_format_space
|
||||
from hyperglass.exceptions import HyperglassError
|
||||
from hyperglass.util import log
|
||||
|
||||
|
||||
class Construct:
|
||||
"""
|
||||
Constructs SSH commands or REST API queries based on validated
|
||||
input parameters.
|
||||
"""
|
||||
"""Construct SSH commands/REST API parameters from validated query data."""
|
||||
|
||||
def get_device_vrf(self):
|
||||
_device_vrf = None
|
||||
|
@@ -1,3 +1,5 @@
|
||||
"""Handle JSON Web Token Encoding & Decoding."""
|
||||
|
||||
# Standard Library Imports
|
||||
import datetime
|
||||
|
||||
@@ -9,7 +11,7 @@ from hyperglass.exceptions import RestError
|
||||
|
||||
|
||||
async def jwt_decode(payload, secret):
|
||||
"""Decode & validate an encoded JSON Web Token (JWT)"""
|
||||
"""Decode & validate an encoded JSON Web Token (JWT)."""
|
||||
try:
|
||||
decoded = jwt.decode(payload, secret, algorithm="HS256")
|
||||
decoded = decoded["payload"]
|
||||
@@ -19,7 +21,7 @@ async def jwt_decode(payload, secret):
|
||||
|
||||
|
||||
async def jwt_encode(payload, secret, duration):
|
||||
"""Encode a query to a JSON Web Token (JWT)"""
|
||||
"""Encode a query to a JSON Web Token (JWT)."""
|
||||
token = {
|
||||
"payload": payload,
|
||||
"nbf": datetime.datetime.utcnow(),
|
||||
|
@@ -1,4 +1,5 @@
|
||||
"""
|
||||
"""Execute validated & constructed query on device.
|
||||
|
||||
Accepts input from front end application, validates the input and
|
||||
returns errors if input is invalid. Passes validated parameters to
|
||||
construct.py, which is used to build & run the Netmiko connectoins or
|
||||
@@ -11,7 +12,6 @@ import re
|
||||
# Third Party Imports
|
||||
import httpx
|
||||
import sshtunnel
|
||||
from logzero import logger as log
|
||||
from netmiko import ConnectHandler
|
||||
from netmiko import NetMikoAuthenticationException
|
||||
from netmiko import NetmikoAuthError
|
||||
@@ -20,10 +20,10 @@ from netmiko import NetMikoTimeoutException
|
||||
|
||||
# Project Imports
|
||||
from hyperglass.command.construct import Construct
|
||||
from hyperglass.command.encode import jwt_decode
|
||||
from hyperglass.command.encode import jwt_encode
|
||||
from hyperglass.command.validate import Validate
|
||||
from hyperglass.command.encode import jwt_decode, jwt_encode
|
||||
from hyperglass.configuration import devices
|
||||
from hyperglass.configuration import logzero_config # noqa: F401
|
||||
from hyperglass.configuration import params
|
||||
from hyperglass.constants import Supported
|
||||
from hyperglass.constants import protocol_map
|
||||
@@ -32,6 +32,7 @@ from hyperglass.exceptions import DeviceTimeout
|
||||
from hyperglass.exceptions import ResponseEmpty
|
||||
from hyperglass.exceptions import RestError
|
||||
from hyperglass.exceptions import ScrapeError
|
||||
from hyperglass.util import log
|
||||
|
||||
|
||||
class Connect:
|
||||
@@ -219,7 +220,6 @@ class Connect:
|
||||
"""Sends HTTP POST to router running a hyperglass API agent"""
|
||||
log.debug(f"Query parameters: {self.query}")
|
||||
|
||||
# uri = Supported.map_rest(self.device.nos)
|
||||
headers = {"Content-Type": "application/json"}
|
||||
http_protocol = protocol_map.get(self.device.port, "https")
|
||||
endpoint = "{protocol}://{addr}:{port}/query".format(
|
||||
|
@@ -1,4 +1,5 @@
|
||||
"""
|
||||
"""Validate query data.
|
||||
|
||||
Accepts raw input data from execute.py, passes it through specific
|
||||
filters based on query type, returns validity boolean and specific
|
||||
error message.
|
||||
@@ -7,15 +8,12 @@ error message.
|
||||
import ipaddress
|
||||
import re
|
||||
|
||||
# Third Party Imports
|
||||
from logzero import logger as log
|
||||
|
||||
# Project Imports
|
||||
from hyperglass.configuration import logzero_config # noqa: F401
|
||||
from hyperglass.configuration import params
|
||||
from hyperglass.exceptions import HyperglassError
|
||||
from hyperglass.exceptions import InputInvalid
|
||||
from hyperglass.exceptions import InputNotAllowed
|
||||
from hyperglass.util import log
|
||||
|
||||
|
||||
class IPType:
|
||||
|
@@ -1,24 +1,22 @@
|
||||
"""
|
||||
Imports configuration varibles from configuration files and returns
|
||||
default values if undefined.
|
||||
"""
|
||||
"""Import configuration files and returns default values if undefined."""
|
||||
|
||||
# Standard Library Imports
|
||||
from pathlib import Path
|
||||
|
||||
# Third Party Imports
|
||||
import logzero
|
||||
import yaml
|
||||
from logzero import logger as log
|
||||
from pydantic import ValidationError
|
||||
|
||||
# Project Imports
|
||||
from hyperglass.configuration.models import commands as _commands
|
||||
from hyperglass.configuration.models import params as _params
|
||||
from hyperglass.configuration.models import routers as _routers
|
||||
from hyperglass.constants import LOG_HANDLER
|
||||
from hyperglass.constants import LOG_LEVELS
|
||||
from hyperglass.exceptions import ConfigError
|
||||
from hyperglass.exceptions import ConfigInvalid
|
||||
from hyperglass.exceptions import ConfigMissing
|
||||
from hyperglass.util import log
|
||||
|
||||
# Project Directories
|
||||
working_dir = Path(__file__).resolve().parent
|
||||
@@ -82,19 +80,14 @@ except ValidationError as validation_errors:
|
||||
)
|
||||
|
||||
|
||||
# Logzero Configuration
|
||||
log_level = 20
|
||||
# Logging Config
|
||||
if params.general.debug:
|
||||
log_level = 10
|
||||
log_format = (
|
||||
"%(color)s[%(asctime)s.%(msecs)03d %(module)s:%(funcName)s:%(lineno)d "
|
||||
"%(levelname)s]%(end_color)s %(message)s"
|
||||
)
|
||||
date_format = "%Y-%m-%d %H:%M:%S"
|
||||
logzero_formatter = logzero.LogFormatter(fmt=log_format, datefmt=date_format)
|
||||
logzero_config = logzero.setup_default_logger(
|
||||
formatter=logzero_formatter, level=log_level
|
||||
)
|
||||
_log_level = "DEBUG"
|
||||
LOG_HANDLER["level"] = _log_level
|
||||
log.remove()
|
||||
log.configure(handlers=[LOG_HANDLER], levels=LOG_LEVELS)
|
||||
|
||||
log.debug("Debugging Enabled")
|
||||
|
||||
|
||||
def build_frontend_networks():
|
||||
|
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
Utility Functions for Pydantic Models
|
||||
"""
|
||||
"""Utility Functions for Pydantic Models."""
|
||||
|
||||
# Standard Library Imports
|
||||
import re
|
||||
@@ -10,10 +8,17 @@ from pydantic import BaseSettings
|
||||
|
||||
|
||||
def clean_name(_name):
|
||||
"""
|
||||
Converts any "desirable" seperators to underscore, then
|
||||
removes all characters that are unsupported in Python class
|
||||
variable names. Also removes leading numbers underscores.
|
||||
"""Remove unsupported characters from field names.
|
||||
|
||||
Converts any "desirable" seperators to underscore, then removes all
|
||||
characters that are unsupported in Python class variable names.
|
||||
Also removes leading numbers underscores.
|
||||
|
||||
Arguments:
|
||||
_name {str} -- Initial field name
|
||||
|
||||
Returns:
|
||||
{str} -- Cleaned field name
|
||||
"""
|
||||
_replaced = re.sub(r"[\-|\.|\@|\~|\:\/|\s]", "_", _name)
|
||||
_scrubbed = "".join(re.findall(r"([a-zA-Z]\w+|\_+)", _replaced))
|
||||
@@ -21,12 +26,15 @@ def clean_name(_name):
|
||||
|
||||
|
||||
class HyperglassModel(BaseSettings):
|
||||
"""Base model for all hyperglass configuration models"""
|
||||
"""Base model for all hyperglass configuration models."""
|
||||
|
||||
pass
|
||||
|
||||
class Config:
|
||||
"""Default pydantic configuration"""
|
||||
"""Default Pydantic configuration.
|
||||
|
||||
See https://pydantic-docs.helpmanual.io/usage/model_config
|
||||
"""
|
||||
|
||||
validate_all = True
|
||||
extra = "forbid"
|
||||
@@ -35,11 +43,11 @@ class HyperglassModel(BaseSettings):
|
||||
|
||||
|
||||
class HyperglassModelExtra(HyperglassModel):
|
||||
"""Model for hyperglass configuration models with dynamic fields"""
|
||||
"""Model for hyperglass configuration models with dynamic fields."""
|
||||
|
||||
pass
|
||||
|
||||
class Config:
|
||||
"""Default pydantic configuration"""
|
||||
"""Default pydantic configuration."""
|
||||
|
||||
extra = "allow"
|
||||
|
@@ -1,10 +1,4 @@
|
||||
"""
|
||||
Defines models for all Branding variables.
|
||||
|
||||
Imports config variables and overrides default class attributes.
|
||||
|
||||
Validates input for overridden parameters.
|
||||
"""
|
||||
"""Validate branding configuration variables."""
|
||||
|
||||
# Third Party Imports
|
||||
from pydantic import constr
|
||||
|
@@ -1,10 +1,5 @@
|
||||
"""
|
||||
Defines models for all config variables.
|
||||
"""Validate command configuration variables."""
|
||||
|
||||
Imports config variables and overrides default class attributes.
|
||||
|
||||
Validates input for overridden parameters.
|
||||
"""
|
||||
# Disable string length warnings so I can actually read these commands
|
||||
# flake8: noqa: E501
|
||||
|
||||
|
@@ -1,10 +1,4 @@
|
||||
"""
|
||||
Defines models for Credential config variables.
|
||||
|
||||
Imports config variables and overrides default class attributes.
|
||||
|
||||
Validates input for overridden parameters.
|
||||
"""
|
||||
"""Validate credential configuration variables."""
|
||||
|
||||
# Third Party Imports
|
||||
from pydantic import SecretStr
|
||||
@@ -15,14 +9,14 @@ from hyperglass.configuration.models._utils import clean_name
|
||||
|
||||
|
||||
class Credential(HyperglassModel):
|
||||
"""Model for per-credential config in devices.yaml"""
|
||||
"""Model for per-credential config in devices.yaml."""
|
||||
|
||||
username: str
|
||||
password: SecretStr
|
||||
|
||||
|
||||
class Credentials(HyperglassModel):
|
||||
"""Base model for credentials class"""
|
||||
"""Base model for credentials class."""
|
||||
|
||||
@classmethod
|
||||
def import_params(cls, input_params):
|
||||
|
@@ -1,10 +1,5 @@
|
||||
"""
|
||||
Defines models for all Features variables.
|
||||
"""Validate feature configuration variables."""
|
||||
|
||||
Imports config variables and overrides default class attributes.
|
||||
|
||||
Validates input for overridden parameters.
|
||||
"""
|
||||
# Standard Library Imports
|
||||
from math import ceil
|
||||
|
||||
@@ -16,20 +11,20 @@ from hyperglass.configuration.models._utils import HyperglassModel
|
||||
|
||||
|
||||
class Features(HyperglassModel):
|
||||
"""Class model for params.features"""
|
||||
"""Validation model for params.features."""
|
||||
|
||||
class BgpRoute(HyperglassModel):
|
||||
"""Class model for params.features.bgp_route"""
|
||||
"""Validation model for params.features.bgp_route."""
|
||||
|
||||
enable: bool = True
|
||||
|
||||
class BgpCommunity(HyperglassModel):
|
||||
"""Class model for params.features.bgp_community"""
|
||||
"""Validation model for params.features.bgp_community."""
|
||||
|
||||
enable: bool = True
|
||||
|
||||
class Regex(HyperglassModel):
|
||||
"""Class model for params.features.bgp_community.regex"""
|
||||
"""Validation model for params.features.bgp_community.regex."""
|
||||
|
||||
decimal: str = r"^[0-9]{1,10}$"
|
||||
extended_as: str = r"^([0-9]{0,5})\:([0-9]{1,5})$"
|
||||
@@ -38,12 +33,12 @@ class Features(HyperglassModel):
|
||||
regex: Regex = Regex()
|
||||
|
||||
class BgpAsPath(HyperglassModel):
|
||||
"""Class model for params.features.bgp_aspath"""
|
||||
"""Validation model for params.features.bgp_aspath."""
|
||||
|
||||
enable: bool = True
|
||||
|
||||
class Regex(HyperglassModel):
|
||||
"""Class model for params.bgp_aspath.regex"""
|
||||
"""Validation model for params.bgp_aspath.regex."""
|
||||
|
||||
mode: constr(regex="asplain|asdot") = "asplain"
|
||||
asplain: str = r"^(\^|^\_)(\d+\_|\d+\$|\d+\(\_\.\+\_\))+$"
|
||||
@@ -54,17 +49,17 @@ class Features(HyperglassModel):
|
||||
regex: Regex = Regex()
|
||||
|
||||
class Ping(HyperglassModel):
|
||||
"""Class model for params.features.ping"""
|
||||
"""Validation model for params.features.ping."""
|
||||
|
||||
enable: bool = True
|
||||
|
||||
class Traceroute(HyperglassModel):
|
||||
"""Class model for params.features.traceroute"""
|
||||
"""Validation model for params.features.traceroute."""
|
||||
|
||||
enable: bool = True
|
||||
|
||||
class Cache(HyperglassModel):
|
||||
"""Class model for params.features.cache"""
|
||||
"""Validation model for params.features.cache."""
|
||||
|
||||
redis_id: int = 0
|
||||
timeout: int = 120
|
||||
@@ -74,7 +69,7 @@ class Features(HyperglassModel):
|
||||
)
|
||||
|
||||
class MaxPrefix(HyperglassModel):
|
||||
"""Class model for params.features.max_prefix"""
|
||||
"""Validation model for params.features.max_prefix."""
|
||||
|
||||
enable: bool = False
|
||||
ipv4: int = 24
|
||||
@@ -84,12 +79,12 @@ class Features(HyperglassModel):
|
||||
)
|
||||
|
||||
class RateLimit(HyperglassModel):
|
||||
"""Class model for params.features.rate_limit"""
|
||||
"""Validation model for params.features.rate_limit."""
|
||||
|
||||
redis_id: int = 1
|
||||
|
||||
class Query(HyperglassModel):
|
||||
"""Class model for params.features.rate_limit.query"""
|
||||
"""Validation model for params.features.rate_limit.query."""
|
||||
|
||||
rate: int = 5
|
||||
period: str = "minute"
|
||||
@@ -101,7 +96,7 @@ class Features(HyperglassModel):
|
||||
button: str = "Try Again"
|
||||
|
||||
class Site(HyperglassModel):
|
||||
"""Class model for params.features.rate_limit.site"""
|
||||
"""Validation model for params.features.rate_limit.site."""
|
||||
|
||||
rate: int = 60
|
||||
period: str = "minute"
|
||||
|
@@ -1,10 +1,5 @@
|
||||
"""
|
||||
Defines models for General config variables.
|
||||
"""Validate general configuration variables."""
|
||||
|
||||
Imports config variables and overrides default class attributes.
|
||||
|
||||
Validates input for overridden parameters.
|
||||
"""
|
||||
# Standard Library Imports
|
||||
from typing import List
|
||||
|
||||
@@ -13,7 +8,7 @@ from hyperglass.configuration.models._utils import HyperglassModel
|
||||
|
||||
|
||||
class General(HyperglassModel):
|
||||
"""Class model for params.general"""
|
||||
"""Validation model for params.general."""
|
||||
|
||||
debug: bool = False
|
||||
primary_asn: str = "65001"
|
||||
|
@@ -1,17 +1,11 @@
|
||||
"""
|
||||
Defines models for Messages config variables.
|
||||
|
||||
Imports config variables and overrides default class attributes.
|
||||
|
||||
Validates input for overridden parameters.
|
||||
"""
|
||||
"""Validate error message configuration variables."""
|
||||
|
||||
# Project Imports
|
||||
from hyperglass.configuration.models._utils import HyperglassModel
|
||||
|
||||
|
||||
class Messages(HyperglassModel):
|
||||
"""Class model for params.messages"""
|
||||
"""Validation model for params.messages."""
|
||||
|
||||
no_input: str = "{field} must be specified."
|
||||
acl_denied: str = "{target} is a member of {denied_network}, which is not allowed."
|
||||
|
@@ -1,10 +1,4 @@
|
||||
"""
|
||||
Defines models for Networks config variables.
|
||||
|
||||
Imports config variables and overrides default class attributes.
|
||||
|
||||
Validates input for overridden parameters.
|
||||
"""
|
||||
"""Validate network configuration variables."""
|
||||
|
||||
# Project Imports
|
||||
from hyperglass.configuration.models._utils import HyperglassModel
|
||||
@@ -12,14 +6,14 @@ from hyperglass.configuration.models._utils import clean_name
|
||||
|
||||
|
||||
class Network(HyperglassModel):
|
||||
"""Model for per-network/asn config in devices.yaml"""
|
||||
"""Validation Model for per-network/asn config in devices.yaml."""
|
||||
|
||||
name: str
|
||||
display_name: str
|
||||
|
||||
|
||||
class Networks(HyperglassModel):
|
||||
"""Base model for networks class"""
|
||||
"""Base model for networks class."""
|
||||
|
||||
@classmethod
|
||||
def import_params(cls, input_params):
|
||||
|
@@ -1,10 +1,4 @@
|
||||
"""
|
||||
Defines models for all Params variables.
|
||||
|
||||
Imports config variables and overrides default class attributes.
|
||||
|
||||
Validates input for overridden parameters.
|
||||
"""
|
||||
"""Configuration validation entry point."""
|
||||
|
||||
# Project Imports
|
||||
from hyperglass.configuration.models._utils import HyperglassModel
|
||||
@@ -15,7 +9,7 @@ from hyperglass.configuration.models.messages import Messages
|
||||
|
||||
|
||||
class Params(HyperglassModel):
|
||||
"""Base model for params"""
|
||||
"""Validation model for all configuration variables."""
|
||||
|
||||
general: General = General()
|
||||
features: Features = Features()
|
||||
|
@@ -1,10 +1,4 @@
|
||||
"""
|
||||
Defines models for Router config variables.
|
||||
|
||||
Imports config variables and overrides default class attributes.
|
||||
|
||||
Validates input for overridden parameters.
|
||||
"""
|
||||
"""Validate SSH proxy configuration variables."""
|
||||
|
||||
# Third Party Imports
|
||||
from pydantic import validator
|
||||
@@ -17,7 +11,7 @@ from hyperglass.exceptions import UnsupportedDevice
|
||||
|
||||
|
||||
class Proxy(HyperglassModel):
|
||||
"""Model for per-proxy config in devices.yaml"""
|
||||
"""Validation model for per-proxy config in devices.yaml."""
|
||||
|
||||
name: str
|
||||
address: str
|
||||
@@ -26,17 +20,17 @@ class Proxy(HyperglassModel):
|
||||
nos: str = "linux_ssh"
|
||||
|
||||
@validator("nos")
|
||||
def supported_nos(cls, v): # noqa: N805
|
||||
def supported_nos(cls, value): # noqa: N805
|
||||
"""
|
||||
Validates that passed nos string is supported by hyperglass.
|
||||
"""
|
||||
if not v == "linux_ssh":
|
||||
raise UnsupportedDevice(f'"{v}" device type is not supported.')
|
||||
return v
|
||||
if not value == "linux_ssh":
|
||||
raise UnsupportedDevice(f'"{value}" device type is not supported.')
|
||||
return value
|
||||
|
||||
|
||||
class Proxies(HyperglassModel):
|
||||
"""Base model for proxies class"""
|
||||
"""Validation model for SSH proxy configuration."""
|
||||
|
||||
@classmethod
|
||||
def import_params(cls, input_params):
|
||||
|
@@ -1,10 +1,5 @@
|
||||
"""
|
||||
Defines models for Router config variables.
|
||||
"""Validate router configuration variables."""
|
||||
|
||||
Imports config variables and overrides default class attributes.
|
||||
|
||||
Validates input for overridden parameters.
|
||||
"""
|
||||
# Standard Library Imports
|
||||
import re
|
||||
from typing import List
|
||||
@@ -12,7 +7,6 @@ from typing import Union
|
||||
|
||||
# Third Party Imports
|
||||
from pydantic import validator
|
||||
from logzero import logger as log
|
||||
|
||||
# Project Imports
|
||||
from hyperglass.configuration.models._utils import HyperglassModel
|
||||
@@ -22,14 +16,15 @@ from hyperglass.configuration.models.commands import Command
|
||||
from hyperglass.configuration.models.credentials import Credential
|
||||
from hyperglass.configuration.models.networks import Network
|
||||
from hyperglass.configuration.models.proxies import Proxy
|
||||
from hyperglass.configuration.models.vrfs import Vrf, DefaultVrf
|
||||
from hyperglass.configuration.models.vrfs import DefaultVrf, Vrf
|
||||
from hyperglass.constants import Supported
|
||||
from hyperglass.exceptions import ConfigError
|
||||
from hyperglass.exceptions import UnsupportedDevice
|
||||
from hyperglass.util import log
|
||||
|
||||
|
||||
class Router(HyperglassModel):
|
||||
"""Model for per-router config in devices.yaml."""
|
||||
"""Validation model for per-router config in devices.yaml."""
|
||||
|
||||
name: str
|
||||
address: str
|
||||
@@ -129,7 +124,7 @@ class Router(HyperglassModel):
|
||||
|
||||
|
||||
class Routers(HyperglassModelExtra):
|
||||
"""Base model for devices class."""
|
||||
"""Validation model for device configurations."""
|
||||
|
||||
hostnames: List[str] = []
|
||||
vrfs: List[str] = []
|
||||
|
@@ -1,10 +1,5 @@
|
||||
"""
|
||||
Defines models for VRF config variables.
|
||||
"""Validate VRF configuration variables."""
|
||||
|
||||
Imports config variables and overrides default class attributes.
|
||||
|
||||
Validates input for overridden parameters.
|
||||
"""
|
||||
# Standard Library Imports
|
||||
from ipaddress import IPv4Address
|
||||
from ipaddress import IPv4Network
|
||||
@@ -25,7 +20,7 @@ from hyperglass.exceptions import ConfigError
|
||||
|
||||
|
||||
class DeviceVrf4(HyperglassModel):
|
||||
"""Model for AFI definitions"""
|
||||
"""Validation model for IPv4 AFI definitions."""
|
||||
|
||||
vrf_name: str
|
||||
source_address: IPv4Address
|
||||
@@ -46,7 +41,7 @@ class DeviceVrf4(HyperglassModel):
|
||||
|
||||
|
||||
class DeviceVrf6(HyperglassModel):
|
||||
"""Model for AFI definitions"""
|
||||
"""Validation model for IPv6 AFI definitions."""
|
||||
|
||||
vrf_name: str
|
||||
source_address: IPv6Address
|
||||
@@ -67,7 +62,7 @@ class DeviceVrf6(HyperglassModel):
|
||||
|
||||
|
||||
class Vrf(HyperglassModel):
|
||||
"""Model for per VRF/afi config in devices.yaml"""
|
||||
"""Validation model for per VRF/afi config in devices.yaml."""
|
||||
|
||||
name: str
|
||||
display_name: str
|
||||
@@ -94,16 +89,21 @@ class Vrf(HyperglassModel):
|
||||
|
||||
|
||||
class DefaultVrf(HyperglassModel):
|
||||
"""Validation model for default routing table VRF."""
|
||||
|
||||
name: str = "default"
|
||||
display_name: str = "Global"
|
||||
access_list = [{"allow": IPv4Network("0.0.0.0/0")}, {"allow": IPv6Network("::/0")}]
|
||||
|
||||
class DefaultVrf4(HyperglassModel):
|
||||
"""Validation model for IPv4 default routing table VRF definition."""
|
||||
|
||||
vrf_name: str = "default"
|
||||
source_address: IPv4Address = IPv4Address("127.0.0.1")
|
||||
|
||||
class DefaultVrf6(HyperglassModel):
|
||||
"""Validation model for IPv6 default routing table VRF definition."""
|
||||
|
||||
vrf_name: str = "default"
|
||||
source_address: IPv6Address = IPv6Address("::1")
|
||||
|
||||
|
@@ -1,11 +1,25 @@
|
||||
"""
|
||||
Global Constants for hyperglass
|
||||
"""
|
||||
"""Constant definitions used throughout the application."""
|
||||
import sys
|
||||
|
||||
protocol_map = {80: "http", 8080: "http", 443: "https", 8443: "https"}
|
||||
|
||||
target_format_space = ("huawei", "huawei_vrpv8")
|
||||
|
||||
LOG_FMT = (
|
||||
"<lvl><b>[{level}]</b> {time:YYYYMMDD} <lw>|</lw> {time:HH:mm:ss} {name} "
|
||||
"<lw>|</lw> {function}</lvl> <lvl><b>→</b></lvl> {message}"
|
||||
)
|
||||
LOG_LEVELS = [
|
||||
{"name": "DEBUG", "no": 10, "color": "<c>"},
|
||||
{"name": "INFO", "no": 20, "color": "<le>"},
|
||||
{"name": "SUCCESS", "no": 25, "color": "<g>"},
|
||||
{"name": "WARNING", "no": 30, "color": "<y>"},
|
||||
{"name": "ERROR", "no": 40, "color": "<y>"},
|
||||
{"name": "CRITICAL", "no": 50, "color": "<r>"},
|
||||
]
|
||||
|
||||
LOG_HANDLER = {"sink": sys.stdout, "format": LOG_FMT, "level": "INFO"}
|
||||
|
||||
|
||||
class Supported:
|
||||
"""
|
||||
|
@@ -1,137 +1,161 @@
|
||||
"""
|
||||
Custom exceptions for hyperglass
|
||||
"""
|
||||
"""Custom exceptions for hyperglass."""
|
||||
|
||||
import json as _json
|
||||
from hyperglass.util import log
|
||||
|
||||
|
||||
class HyperglassError(Exception):
|
||||
"""hyperglass base exception"""
|
||||
"""hyperglass base exception."""
|
||||
|
||||
def __init__(self, message="", alert="warning", keywords=[]):
|
||||
def __init__(self, message="", alert="warning", keywords=None):
|
||||
"""Initialize the hyperglass base exception class.
|
||||
|
||||
Keyword Arguments:
|
||||
message {str} -- Error message (default: {""})
|
||||
alert {str} -- Error severity (default: {"warning"})
|
||||
keywords {list} -- 'Important' keywords (default: {None})
|
||||
"""
|
||||
self.message = message
|
||||
self.alert = alert
|
||||
self.keywords = keywords
|
||||
self.keywords = keywords or []
|
||||
if self.alert == "warning":
|
||||
log.error(repr(self))
|
||||
elif self.alert == "danger":
|
||||
log.critical(repr(self))
|
||||
else:
|
||||
log.info(repr(self))
|
||||
|
||||
def __str__(self):
|
||||
"""Return the instance's error message.
|
||||
|
||||
Returns:
|
||||
{str} -- Error Message
|
||||
"""
|
||||
return self.message
|
||||
|
||||
def __repr__(self):
|
||||
"""Return the instance's severity & error message in a string.
|
||||
|
||||
Returns:
|
||||
{str} -- Error message with code
|
||||
"""
|
||||
return f"[{self.alert.upper()}] {self.message}"
|
||||
|
||||
def __dict__(self):
|
||||
"""Return the instance's attributes as a dictionary.
|
||||
|
||||
Returns:
|
||||
{dict} -- Exception attributes in dict
|
||||
"""
|
||||
return {"message": self.message, "alert": self.alert, "keywords": self.keywords}
|
||||
|
||||
def json(self):
|
||||
"""Return the instance's attributes as a JSON object.
|
||||
|
||||
class ConfigError(HyperglassError):
|
||||
Returns:
|
||||
{str} -- Exception attributes as JSON
|
||||
"""
|
||||
return _json.dumps(self.__dict__())
|
||||
|
||||
@property
|
||||
def message(self):
|
||||
"""Return the instance's `message` attribute.
|
||||
|
||||
Returns:
|
||||
{str} -- Error Message
|
||||
"""
|
||||
return self.message
|
||||
|
||||
@property
|
||||
def alert(self):
|
||||
"""Return the instance's `alert` attribute.
|
||||
|
||||
Returns:
|
||||
{str} -- Alert name
|
||||
"""
|
||||
return self.alert
|
||||
|
||||
@property
|
||||
def keywords(self):
|
||||
"""Return the instance's `keywords` attribute.
|
||||
|
||||
Returns:
|
||||
{list} -- Keywords List
|
||||
"""
|
||||
return self.keywords
|
||||
|
||||
|
||||
class _UnformattedHyperglassError(HyperglassError):
|
||||
"""Base exception class for freeform error messages."""
|
||||
|
||||
def __init__(self, unformatted_msg, alert="warning", **kwargs):
|
||||
"""Format error message with keyword arguments.
|
||||
|
||||
Keyword Arguments:
|
||||
message {str} -- Error message (default: {""})
|
||||
alert {str} -- Error severity (default: {"warning"})
|
||||
keywords {list} -- 'Important' keywords (default: {None})
|
||||
"""
|
||||
self.message = unformatted_msg.format(**kwargs)
|
||||
self.alert = alert
|
||||
self.keywords = list(kwargs.values())
|
||||
super().__init__(message=self.message, alert=self.alert, keywords=self.keywords)
|
||||
|
||||
|
||||
class ConfigError(_UnformattedHyperglassError):
|
||||
"""Raised for generic user-config issues."""
|
||||
|
||||
def __init__(self, unformatted_msg, **kwargs):
|
||||
self.message = unformatted_msg.format(**kwargs)
|
||||
self.keywords = [value for value in kwargs.values()]
|
||||
super().__init__(message=self.message, keywords=self.keywords)
|
||||
|
||||
class ConfigInvalid(_UnformattedHyperglassError):
|
||||
"""Raised when a config item fails type or option validation."""
|
||||
|
||||
message = 'The value field "{field}" is invalid: {error_msg}'
|
||||
|
||||
|
||||
class ConfigInvalid(HyperglassError):
|
||||
"""Raised when a config item fails type or option validation"""
|
||||
class ConfigMissing(_UnformattedHyperglassError):
|
||||
"""Raised when a required config file or item is missing or undefined."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.message = 'The value field "{field}" is invalid: {error_msg}'.format(
|
||||
**kwargs
|
||||
)
|
||||
self.keywords = [value for value in kwargs.values()]
|
||||
super().__init__(message=self.message, keywords=self.keywords)
|
||||
message = (
|
||||
"{missing_item} is missing or undefined and is required to start "
|
||||
"hyperglass. Please consult the installation documentation."
|
||||
)
|
||||
|
||||
|
||||
class ConfigMissing(HyperglassError):
|
||||
"""
|
||||
Raised when a required config file or item is missing or undefined.
|
||||
"""
|
||||
class ScrapeError(_UnformattedHyperglassError):
|
||||
"""Raised when a scrape/netmiko error occurs."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.message = (
|
||||
"{missing_item} is missing or undefined and is required to start "
|
||||
"hyperglass. Please consult the installation documentation."
|
||||
).format(**kwargs)
|
||||
self.keywords = [value for value in kwargs.values()]
|
||||
super().__init__(message=self.message, keywords=self.keywords)
|
||||
alert = "danger"
|
||||
|
||||
|
||||
class ScrapeError(HyperglassError):
|
||||
"""Raised upon a scrape/netmiko error"""
|
||||
class AuthError(_UnformattedHyperglassError):
|
||||
"""Raised when authentication to a device fails."""
|
||||
|
||||
def __init__(self, msg, **kwargs):
|
||||
self.message = msg.format(**kwargs)
|
||||
self.alert = "danger"
|
||||
self.keywords = [value for value in kwargs.values()]
|
||||
super().__init__(message=self.message, alert=self.alert, keywords=self.keywords)
|
||||
alert = "danger"
|
||||
|
||||
|
||||
class AuthError(HyperglassError):
|
||||
"""Raised when authentication to a device fails"""
|
||||
class RestError(_UnformattedHyperglassError):
|
||||
"""Raised upon a rest API client error."""
|
||||
|
||||
def __init__(self, msg, **kwargs):
|
||||
self.message = msg.format(**kwargs)
|
||||
self.alert = "danger"
|
||||
self.keywords = [value for value in kwargs.values()]
|
||||
super().__init__(message=self.message, alert=self.alert, keywords=self.keywords)
|
||||
alert = "danger"
|
||||
|
||||
|
||||
class RestError(HyperglassError):
|
||||
"""Raised upon a rest API client error"""
|
||||
|
||||
def __init__(self, msg, **kwargs):
|
||||
self.message = msg.format(**kwargs)
|
||||
self.alert = "danger"
|
||||
self.keywords = [value for value in kwargs.values()]
|
||||
super().__init__(message=self.message, alert=self.alert, keywords=self.keywords)
|
||||
|
||||
|
||||
class InputInvalid(HyperglassError):
|
||||
"""Raised when input validation fails"""
|
||||
|
||||
def __init__(self, unformatted_msg, **kwargs):
|
||||
self.message = unformatted_msg.format(**kwargs)
|
||||
self.alert = "warning"
|
||||
self.keywords = [value for value in kwargs.values()]
|
||||
super().__init__(message=self.message, alert=self.alert, keywords=self.keywords)
|
||||
|
||||
|
||||
class InputNotAllowed(HyperglassError):
|
||||
"""
|
||||
Raised when input validation fails due to a blacklist or
|
||||
requires_ipv6_cidr check
|
||||
"""
|
||||
|
||||
def __init__(self, unformatted_msg, **kwargs):
|
||||
self.message = unformatted_msg.format(**kwargs)
|
||||
self.alert = "warning"
|
||||
self.keywords = [value for value in kwargs.values()]
|
||||
super().__init__(message=self.message, alert=self.alert, keywords=self.keywords)
|
||||
|
||||
|
||||
class ResponseEmpty(HyperglassError):
|
||||
"""
|
||||
Raised when hyperglass is able to connect to the device and execute
|
||||
a valid query, but the response is empty.
|
||||
"""
|
||||
|
||||
def __init__(self, unformatted_msg, **kwargs):
|
||||
self.message = unformatted_msg.format(**kwargs)
|
||||
self.alert = "warning"
|
||||
self.keywords = [value for value in kwargs.values()]
|
||||
super().__init__(message=self.message, alert=self.alert, keywords=self.keywords)
|
||||
|
||||
|
||||
class UnsupportedDevice(HyperglassError):
|
||||
"""Raised when an input NOS is not in the supported NOS list."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.message = "".format(**kwargs)
|
||||
self.keywords = [value for value in kwargs.values()]
|
||||
super().__init__(message=self.message, keywords=self.keywords)
|
||||
|
||||
|
||||
class DeviceTimeout(HyperglassError):
|
||||
class DeviceTimeout(_UnformattedHyperglassError):
|
||||
"""Raised when the connection to a device times out."""
|
||||
|
||||
def __init__(self, msg, **kwargs):
|
||||
self.message = msg.format(**kwargs)
|
||||
self.alert = "danger"
|
||||
self.keywords = [value for value in kwargs.values()]
|
||||
super().__init__(message=self.message, alert=self.alert, keywords=self.keywords)
|
||||
alert = "danger"
|
||||
|
||||
|
||||
class InputInvalid(_UnformattedHyperglassError):
|
||||
"""Raised when input validation fails."""
|
||||
|
||||
|
||||
class InputNotAllowed(_UnformattedHyperglassError):
|
||||
"""Raised when input validation fails due to a configured check."""
|
||||
|
||||
|
||||
class ResponseEmpty(_UnformattedHyperglassError):
|
||||
"""Raised when hyperglass can connect to the device but the response is empty."""
|
||||
|
||||
|
||||
class UnsupportedDevice(_UnformattedHyperglassError):
|
||||
"""Raised when an input NOS is not in the supported NOS list."""
|
||||
|
@@ -1,4 +1,4 @@
|
||||
"""Hyperglass Front End"""
|
||||
"""Hyperglass Front End."""
|
||||
|
||||
# Standard Library Imports
|
||||
import operator
|
||||
@@ -8,7 +8,6 @@ from pathlib import Path
|
||||
# Third Party Imports
|
||||
import aredis
|
||||
import stackprinter
|
||||
from logzero import logger as log
|
||||
from prometheus_client import CONTENT_TYPE_LATEST
|
||||
from prometheus_client import CollectorRegistry
|
||||
from prometheus_client import Counter
|
||||
@@ -27,7 +26,6 @@ from sanic_limiter import get_remote_address
|
||||
# Project Imports
|
||||
from hyperglass.command.execute import Execute
|
||||
from hyperglass.configuration import devices
|
||||
from hyperglass.configuration import logzero_config # noqa: F401
|
||||
from hyperglass.configuration import params
|
||||
from hyperglass.constants import Supported
|
||||
from hyperglass.exceptions import AuthError
|
||||
@@ -39,6 +37,7 @@ from hyperglass.exceptions import ResponseEmpty
|
||||
from hyperglass.exceptions import RestError
|
||||
from hyperglass.exceptions import ScrapeError
|
||||
from hyperglass.render import render_html
|
||||
from hyperglass.util import log
|
||||
|
||||
stackprinter.set_excepthook()
|
||||
|
||||
|
@@ -1,20 +1,18 @@
|
||||
"""
|
||||
Renders Jinja2 & Sass templates for use by the front end application
|
||||
"""
|
||||
"""Renders Jinja2 & Sass templates for use by the front end application."""
|
||||
|
||||
# Standard Library Imports
|
||||
from pathlib import Path
|
||||
|
||||
# Third Party Imports
|
||||
import jinja2
|
||||
import yaml
|
||||
from logzero import logger as log
|
||||
from markdown2 import Markdown
|
||||
|
||||
# Project Imports
|
||||
from hyperglass.configuration import logzero_config # NOQA: F401
|
||||
from hyperglass.configuration import networks
|
||||
from hyperglass.configuration import params
|
||||
from hyperglass.exceptions import HyperglassError
|
||||
from hyperglass.util import log
|
||||
|
||||
# Module Directories
|
||||
working_directory = Path(__file__).resolve().parent
|
||||
@@ -190,7 +188,7 @@ def generate_markdown(section, file_name=None):
|
||||
|
||||
|
||||
def render_html(template_name, **kwargs):
|
||||
"""Renders Jinja2 HTML templates"""
|
||||
"""Render Jinja2 HTML templates."""
|
||||
details_name_list = ["footer", "bgp_aspath", "bgp_community"]
|
||||
details_dict = {}
|
||||
for details_name in details_name_list:
|
||||
|
@@ -1,6 +1,5 @@
|
||||
"""
|
||||
Renders Jinja2 & Sass templates for use by the front end application
|
||||
"""
|
||||
"""Renders Jinja2 & Sass templates for use by the front end application."""
|
||||
|
||||
# Standard Library Imports
|
||||
import json
|
||||
import subprocess
|
||||
@@ -8,15 +7,14 @@ from pathlib import Path
|
||||
|
||||
# Third Party Imports
|
||||
import jinja2
|
||||
from logzero import logger as log
|
||||
|
||||
# Project Imports
|
||||
from hyperglass.configuration import frontend_networks
|
||||
from hyperglass.configuration import frontend_devices
|
||||
from hyperglass.configuration import frontend_networks
|
||||
from hyperglass.configuration import frontend_params
|
||||
from hyperglass.configuration import logzero_config # NOQA: F401
|
||||
from hyperglass.configuration import params
|
||||
from hyperglass.exceptions import HyperglassError
|
||||
from hyperglass.util import log
|
||||
|
||||
# Module Directories
|
||||
working_directory = Path(__file__).resolve().parent
|
||||
|
45
hyperglass/util.py
Normal file
45
hyperglass/util.py
Normal file
@@ -0,0 +1,45 @@
|
||||
"""Utility fuctions."""
|
||||
|
||||
# Third Party Imports
|
||||
from loguru import logger as _loguru_logger
|
||||
|
||||
# Project Imports
|
||||
from hyperglass.constants import LOG_HANDLER
|
||||
from hyperglass.constants import LOG_LEVELS
|
||||
from hyperglass.exceptions import ConfigInvalid
|
||||
|
||||
_loguru_logger.remove()
|
||||
_loguru_logger.configure(handlers=[LOG_HANDLER], levels=LOG_LEVELS)
|
||||
|
||||
log = _loguru_logger
|
||||
|
||||
|
||||
async def check_redis(host, port):
|
||||
"""Validate if Redis is running.
|
||||
|
||||
Arguments:
|
||||
host {str} -- IP address or hostname of Redis server
|
||||
port {[type]} -- TCP port of Redis server
|
||||
|
||||
Raises:
|
||||
ConfigInvalid: Raised if redis server is unreachable
|
||||
|
||||
Returns:
|
||||
{bool} -- True if running, False if not
|
||||
"""
|
||||
import asyncio
|
||||
from socket import gaierror
|
||||
|
||||
try:
|
||||
_reader, _writer = await asyncio.open_connection(str(host), int(port))
|
||||
except gaierror:
|
||||
raise ConfigInvalid(
|
||||
"Redis isn't running: {host}:{port} is unreachable/unresolvable.",
|
||||
alert="danger",
|
||||
host=host,
|
||||
port=port,
|
||||
)
|
||||
if _reader or _writer:
|
||||
return True
|
||||
else:
|
||||
return False
|
Reference in New Issue
Block a user