From 08c33f678f14adcbcaac17ef96dc04578e47cc41 Mon Sep 17 00:00:00 2001 From: checktheroads Date: Tue, 31 Dec 2019 18:29:43 -0700 Subject: [PATCH] fix docstrings; refactor to strict types --- .pre-commit-config.yaml | 12 +- hyperglass/configuration/__init__.py | 4 +- hyperglass/configuration/models/__init__.py | 8 +- hyperglass/configuration/models/branding.py | 2 +- hyperglass/configuration/models/commands.py | 320 +++++++++--------- .../configuration/models/credentials.py | 11 +- hyperglass/configuration/models/features.py | 61 ++-- hyperglass/configuration/models/messages.py | 37 +- hyperglass/configuration/models/networks.py | 22 +- hyperglass/configuration/models/proxies.py | 35 +- hyperglass/configuration/models/routers.py | 135 +++++--- hyperglass/configuration/models/vrfs.py | 60 ++-- line_count.svg | 4 +- 13 files changed, 384 insertions(+), 327 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d56953b..755df41 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,10 +1,10 @@ repos: - # - repo: https://github.com/pre-commit/pre-commit-hooks - # rev: v2.3.0 - # hooks: - # - id: flake8 - # stages: - # - push + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.3.0 + hooks: + - id: flake8 + stages: + - push - repo: local hooks: - id: line_count diff --git a/hyperglass/configuration/__init__.py b/hyperglass/configuration/__init__.py index f0a4914..9469e87 100644 --- a/hyperglass/configuration/__init__.py +++ b/hyperglass/configuration/__init__.py @@ -136,7 +136,7 @@ except ValidationError as validation_errors: raise ConfigInvalid( field=": ".join([str(item) for item in error["loc"]]), error_msg=error["msg"], - ) from None + ) # Re-evaluate debug state after config is validated _set_log_level(params.general.debug) @@ -239,7 +239,7 @@ def _build_networks(): """Build filtered JSON Structure of networks & devices for Jinja templates. Raises: - ConfigError: Raised if parsing/building error occurs. + ConfigError: Raised if parsing/building error occurs. Returns: {dict} -- Networks & devices diff --git a/hyperglass/configuration/models/__init__.py b/hyperglass/configuration/models/__init__.py index 53db139..1bff0e6 100644 --- a/hyperglass/configuration/models/__init__.py +++ b/hyperglass/configuration/models/__init__.py @@ -1,7 +1,5 @@ -""" -Defines models for all config variables. +"""Define models for all config variables. -Imports config variables and overrides default class attributes. - -Validates input for overridden parameters. +Import config variables and overrides default class attributes. +Validate input for overridden parameters. """ diff --git a/hyperglass/configuration/models/branding.py b/hyperglass/configuration/models/branding.py index 14be41f..a6d9fb6 100644 --- a/hyperglass/configuration/models/branding.py +++ b/hyperglass/configuration/models/branding.py @@ -69,7 +69,7 @@ class Branding(HyperglassModel): favicons: str = "ui/images/favicons/" @validator("favicons") - def favicons_trailing_slash(cls, value): # noqa: N805 + def favicons_trailing_slash(cls, value): """If the favicons path does not end in a '/', append it.""" chars = list(value) if chars[len(chars) - 1] != "/": diff --git a/hyperglass/configuration/models/commands.py b/hyperglass/configuration/models/commands.py index 0e9f803..0803961 100644 --- a/hyperglass/configuration/models/commands.py +++ b/hyperglass/configuration/models/commands.py @@ -1,50 +1,50 @@ """Validate command configuration variables.""" -# Disable string length warnings so I can actually read these commands -# flake8: noqa: E501 +# Third Party Imports +from pydantic import StrictStr # Project Imports from hyperglass.configuration.models._utils import HyperglassModel class Command(HyperglassModel): - """Class model for non-default commands""" + """Validation model for non-default commands.""" class IPv4(HyperglassModel): - """Class model for non-default dual afi commands""" + """Validation model for non-default dual afi commands.""" - bgp_route: str = "" - bgp_aspath: str = "" - bgp_community: str = "" - ping: str = "" - traceroute: str = "" + bgp_route: StrictStr = "" + bgp_aspath: StrictStr = "" + bgp_community: StrictStr = "" + ping: StrictStr = "" + traceroute: StrictStr = "" class IPv6(HyperglassModel): - """Class model for non-default ipv4 commands""" + """Validation model for non-default ipv4 commands.""" - bgp_route: str = "" - bgp_aspath: str = "" - bgp_community: str = "" - ping: str = "" - traceroute: str = "" + bgp_route: StrictStr = "" + bgp_aspath: StrictStr = "" + bgp_community: StrictStr = "" + ping: StrictStr = "" + traceroute: StrictStr = "" class VPNIPv4(HyperglassModel): - """Class model for non-default ipv6 commands""" + """Validation model for non-default ipv6 commands.""" - bgp_route: str = "" - bgp_aspath: str = "" - bgp_community: str = "" - ping: str = "" - traceroute: str = "" + bgp_route: StrictStr = "" + bgp_aspath: StrictStr = "" + bgp_community: StrictStr = "" + ping: StrictStr = "" + traceroute: StrictStr = "" class VPNIPv6(HyperglassModel): - """Class model for non-default ipv6 commands""" + """Validation model for non-default ipv6 commands.""" - bgp_route: str = "" - bgp_aspath: str = "" - bgp_community: str = "" - ping: str = "" - traceroute: str = "" + bgp_route: StrictStr = "" + bgp_aspath: StrictStr = "" + bgp_community: StrictStr = "" + ping: StrictStr = "" + traceroute: StrictStr = "" ipv4_default: IPv4 = IPv4() ipv6_default: IPv6 = IPv6() @@ -53,13 +53,19 @@ class Command(HyperglassModel): class Commands(HyperglassModel): - """Base class for commands class""" + """Base class for command definitions.""" @classmethod def import_params(cls, input_params): - """ - Imports passed dict from YAML config, dynamically sets - attributes for the commands class. + """Import loaded YAML, initialize per-command definitions. + + Dynamically set attributes for the command class. + + Arguments: + input_params {dict} -- Unvalidated command definitions + + Returns: + {object} -- Validated commands object """ obj = Commands() for (nos, cmds) in input_params.items(): @@ -67,47 +73,47 @@ class Commands(HyperglassModel): return obj class CiscoIOS(Command): - """Class model for default cisco_ios commands""" + """Validation model for default cisco_ios commands.""" class VPNIPv4(Command.VPNIPv4): - """Default commands for dual afi commands""" + """Default commands for dual afi commands.""" - bgp_community: str = "show bgp vpnv4 unicast vrf {vrf} community {target}" - bgp_aspath: str = 'show bgp vpnv4 unicast vrf {vrf} quote-regexp "{target}"' - bgp_route: str = "show bgp vpnv4 unicast vrf {vrf} {target}" - ping: str = "ping vrf {vrf} {target} repeat 5 source {source}" - traceroute: str = ( + bgp_community: StrictStr = "show bgp vpnv4 unicast vrf {vrf} community {target}" + bgp_aspath: StrictStr = 'show bgp vpnv4 unicast vrf {vrf} quote-regexp "{target}"' + bgp_route: StrictStr = "show bgp vpnv4 unicast vrf {vrf} {target}" + ping: StrictStr = "ping vrf {vrf} {target} repeat 5 source {source}" + traceroute: StrictStr = ( "traceroute vrf {vrf} {target} timeout 1 probe 2 source {source}" ) class VPNIPv6(Command.VPNIPv6): - """Default commands for dual afi commands""" + """Default commands for dual afi commands.""" - bgp_community: str = "show bgp vpnv6 unicast vrf {vrf} community {target}" - bgp_aspath: str = 'show bgp vpnv6 unicast vrf {vrf} quote-regexp "{target}"' - bgp_route: str = "show bgp vpnv6 unicast vrf {vrf} {target}" - ping: str = "ping vrf {vrf} {target} repeat 5 source {source}" - traceroute: str = ( + bgp_community: StrictStr = "show bgp vpnv6 unicast vrf {vrf} community {target}" + bgp_aspath: StrictStr = 'show bgp vpnv6 unicast vrf {vrf} quote-regexp "{target}"' + bgp_route: StrictStr = "show bgp vpnv6 unicast vrf {vrf} {target}" + ping: StrictStr = "ping vrf {vrf} {target} repeat 5 source {source}" + traceroute: StrictStr = ( "traceroute vrf {vrf} {target} timeout 1 probe 2 source {source}" ) class IPv4(Command.IPv4): - """Default commands for ipv4 commands""" + """Default commands for ipv4 commands.""" - bgp_community: str = "show bgp ipv4 unicast community {target}" - bgp_aspath: str = 'show bgp ipv4 unicast quote-regexp "{target}"' - bgp_route: str = "show bgp ipv4 unicast {target} | exclude pathid:|Epoch" - ping: str = "ping {target} repeat 5 source {source}" - traceroute: str = "traceroute {target} timeout 1 probe 2 source {source}" + bgp_community: StrictStr = "show bgp ipv4 unicast community {target}" + bgp_aspath: StrictStr = 'show bgp ipv4 unicast quote-regexp "{target}"' + bgp_route: StrictStr = "show bgp ipv4 unicast {target} | exclude pathid:|Epoch" + ping: StrictStr = "ping {target} repeat 5 source {source}" + traceroute: StrictStr = "traceroute {target} timeout 1 probe 2 source {source}" class IPv6(Command.IPv6): - """Default commands for ipv6 commands""" + """Default commands for ipv6 commands.""" - bgp_community: str = "show bgp ipv6 unicast community {target}" - bgp_aspath: str = 'show bgp ipv6 unicast quote-regexp "{target}"' - bgp_route: str = "show bgp ipv6 unicast {target} | exclude pathid:|Epoch" - ping: str = ("ping ipv6 {target} repeat 5 source {source}") - traceroute: str = ( + bgp_community: StrictStr = "show bgp ipv6 unicast community {target}" + bgp_aspath: StrictStr = 'show bgp ipv6 unicast quote-regexp "{target}"' + bgp_route: StrictStr = "show bgp ipv6 unicast {target} | exclude pathid:|Epoch" + ping: StrictStr = ("ping ipv6 {target} repeat 5 source {source}") + traceroute: StrictStr = ( "traceroute ipv6 {target} timeout 1 probe 2 source {source}" ) @@ -117,43 +123,43 @@ class Commands(HyperglassModel): ipv6_vpn: VPNIPv6 = VPNIPv6() class CiscoXR(Command): - """Class model for default cisco_xr commands""" + """Validation model for default cisco_xr commands.""" class IPv4(Command.IPv4): - """Class model for non-default dual afi commands""" + """Validation model for non-default dual afi commands.""" - bgp_route: str = r"show bgp ipv4 unicast {target} | util egrep \\(BGP routing table entry|Path \\#|aggregated by|Origin |Community:|validity| from \\)" - bgp_aspath: str = r"show bgp ipv4 unicast regexp {target} | utility egrep -v \\(BGP |Table |Non-stop\\)" - bgp_community: str = r"show bgp ipv4 unicast community {target} | utility egrep -v \\(BGP |Table |Non-stop\\)" - ping: str = r"ping ipv4 {target} count 5 source {source}" - traceroute: str = r"traceroute ipv4 {target} timeout 1 probe 2 source {source}" + bgp_route: StrictStr = r"show bgp ipv4 unicast {target} | util egrep \\(BGP routing table entry|Path \\#|aggregated by|Origin |Community:|validity| from \\)" + bgp_aspath: StrictStr = r"show bgp ipv4 unicast regexp {target} | utility egrep -v \\(BGP |Table |Non-stop\\)" + bgp_community: StrictStr = r"show bgp ipv4 unicast community {target} | utility egrep -v \\(BGP |Table |Non-stop\\)" + ping: StrictStr = r"ping ipv4 {target} count 5 source {source}" + traceroute: StrictStr = r"traceroute ipv4 {target} timeout 1 probe 2 source {source}" class IPv6(Command.IPv6): - """Class model for non-default ipv4 commands""" + """Validation model for non-default ipv4 commands.""" - bgp_route: str = r"show bgp ipv6 unicast {target} | util egrep \\(BGP routing table entry|Path \\#|aggregated by|Origin |Community:|validity| from \\)" - bgp_aspath: str = r"show bgp ipv6 unicast regexp {target} | utility egrep -v \\(BGP |Table |Non-stop\\)" - bgp_community: str = r"show bgp ipv6 unicast community {target} | utility egrep -v \\(BGP |Table |Non-stop\\)" - ping: str = r"ping ipv6 {target} count 5 source {source}" - traceroute: str = r"traceroute ipv6 {target} timeout 1 probe 2 source {source}" + bgp_route: StrictStr = r"show bgp ipv6 unicast {target} | util egrep \\(BGP routing table entry|Path \\#|aggregated by|Origin |Community:|validity| from \\)" + bgp_aspath: StrictStr = r"show bgp ipv6 unicast regexp {target} | utility egrep -v \\(BGP |Table |Non-stop\\)" + bgp_community: StrictStr = r"show bgp ipv6 unicast community {target} | utility egrep -v \\(BGP |Table |Non-stop\\)" + ping: StrictStr = r"ping ipv6 {target} count 5 source {source}" + traceroute: StrictStr = r"traceroute ipv6 {target} timeout 1 probe 2 source {source}" class VPNIPv4(Command.VPNIPv4): - """Class model for non-default ipv6 commands""" + """Validation model for non-default ipv6 commands.""" - bgp_route: str = r"show bgp vpnv4 unicast vrf {vrf} {target} | util egrep \\(BGP routing table entry|Path \\#|aggregated by|Origin |Community:|validity| from \\)" - bgp_aspath: str = r"show bgp vpnv4 unicast vrf {vrf} regexp {target} | utility egrep -v \\(BGP |Table |Non-stop\\)" - bgp_community: str = r"show bgp vpnv4 unicast vrf {vrf} community {target} | utility egrep -v \\(BGP |Table |Non-stop\\)" - ping: str = r"ping vrf {vrf} {target} count 5 source {source}" - traceroute: str = r"traceroute vrf {vrf} {target} timeout 1 probe 2 source {source}" + bgp_route: StrictStr = r"show bgp vpnv4 unicast vrf {vrf} {target} | util egrep \\(BGP routing table entry|Path \\#|aggregated by|Origin |Community:|validity| from \\)" + bgp_aspath: StrictStr = r"show bgp vpnv4 unicast vrf {vrf} regexp {target} | utility egrep -v \\(BGP |Table |Non-stop\\)" + bgp_community: StrictStr = r"show bgp vpnv4 unicast vrf {vrf} community {target} | utility egrep -v \\(BGP |Table |Non-stop\\)" + ping: StrictStr = r"ping vrf {vrf} {target} count 5 source {source}" + traceroute: StrictStr = r"traceroute vrf {vrf} {target} timeout 1 probe 2 source {source}" class VPNIPv6(Command.VPNIPv6): - """Class model for non-default ipv6 commands""" + """Validation model for non-default ipv6 commands.""" - bgp_route: str = r"show bgp vpnv6 unicast vrf {vrf} {target} | util egrep \\(BGP routing table entry|Path \\#|aggregated by|Origin |Community:|validity| from \\)" - bgp_aspath: str = r"show bgp vpnv6 unicast vrf {vrf} regexp {target} | utility egrep -v \\(BGP |Table |Non-stop\\)" - bgp_community: str = r"show bgp vpnv6 unicast vrf {vrf} community {target} | utility egrep -v \\(BGP |Table |Non-stop\\)" - ping: str = r"ping vrf {vrf} {target} count 5 source {source}" - traceroute: str = r"traceroute vrf {vrf} {target} timeout 1 probe 2 source {source}" + bgp_route: StrictStr = r"show bgp vpnv6 unicast vrf {vrf} {target} | util egrep \\(BGP routing table entry|Path \\#|aggregated by|Origin |Community:|validity| from \\)" + bgp_aspath: StrictStr = r"show bgp vpnv6 unicast vrf {vrf} regexp {target} | utility egrep -v \\(BGP |Table |Non-stop\\)" + bgp_community: StrictStr = r"show bgp vpnv6 unicast vrf {vrf} community {target} | utility egrep -v \\(BGP |Table |Non-stop\\)" + ping: StrictStr = r"ping vrf {vrf} {target} count 5 source {source}" + traceroute: StrictStr = r"traceroute vrf {vrf} {target} timeout 1 probe 2 source {source}" ipv4_default: IPv4 = IPv4() ipv6_default: IPv6 = IPv6() @@ -161,43 +167,43 @@ class Commands(HyperglassModel): ipv6_vpn: VPNIPv6 = VPNIPv6() class Juniper(Command): - """Class model for default juniper commands""" + """Validation model for default juniper commands.""" class IPv4(Command.IPv4): - """Class model for non-default dual afi commands""" + """Validation model for non-default dual afi commands.""" - bgp_route: str = "show route protocol bgp table inet.0 {target} detail" - bgp_aspath: str = "show route protocol bgp table inet.0 aspath-regex {target}" - bgp_community: str = "show route protocol bgp table inet.0 community {target}" - ping: str = "ping inet {target} count 5 source {source}" - traceroute: str = "traceroute inet {target} wait 1 source {source}" + bgp_route: StrictStr = "show route protocol bgp table inet.0 {target} detail" + bgp_aspath: StrictStr = "show route protocol bgp table inet.0 aspath-regex {target}" + bgp_community: StrictStr = "show route protocol bgp table inet.0 community {target}" + ping: StrictStr = "ping inet {target} count 5 source {source}" + traceroute: StrictStr = "traceroute inet {target} wait 1 source {source}" class IPv6(Command.IPv6): - """Class model for non-default ipv4 commands""" + """Validation model for non-default ipv4 commands.""" - bgp_route: str = "show route protocol bgp table inet6.0 {target} detail" - bgp_aspath: str = "show route protocol bgp community {target}" - bgp_community: str = "show route protocol bgp aspath-regex {target}" - ping: str = "ping inet6 {target} count 5 source {source}" - traceroute: str = "traceroute inet6 {target} wait 1 source {source}" + bgp_route: StrictStr = "show route protocol bgp table inet6.0 {target} detail" + bgp_aspath: StrictStr = "show route protocol bgp community {target}" + bgp_community: StrictStr = "show route protocol bgp aspath-regex {target}" + ping: StrictStr = "ping inet6 {target} count 5 source {source}" + traceroute: StrictStr = "traceroute inet6 {target} wait 1 source {source}" class VPNIPv4(Command.VPNIPv4): - """Class model for non-default ipv6 commands""" + """Validation model for non-default ipv6 commands.""" - bgp_route: str = "show route protocol bgp table {vrf} {target} detail" - bgp_aspath: str = "show route protocol bgp table {vrf} aspath-regex {target}" - bgp_community: str = "show route protocol bgp table {vrf} community {target}" - ping: str = "ping inet routing-instance {vrf} {target} count 5 source {source}" - traceroute: str = "traceroute inet routing-instance {vrf} {target} wait 1 source {source}" + bgp_route: StrictStr = "show route protocol bgp table {vrf} {target} detail" + bgp_aspath: StrictStr = "show route protocol bgp table {vrf} aspath-regex {target}" + bgp_community: StrictStr = "show route protocol bgp table {vrf} community {target}" + ping: StrictStr = "ping inet routing-instance {vrf} {target} count 5 source {source}" + traceroute: StrictStr = "traceroute inet routing-instance {vrf} {target} wait 1 source {source}" class VPNIPv6(Command.VPNIPv6): - """Class model for non-default ipv6 commands""" + """Validation model for non-default ipv6 commands.""" - bgp_route: str = "show route protocol bgp table {vrf} {target} detail" - bgp_aspath: str = "show route protocol bgp table {vrf} aspath-regex {target}" - bgp_community: str = "show route protocol bgp table {vrf} community {target}" - ping: str = "ping inet6 routing-instance {vrf} {target} count 5 source {source}" - traceroute: str = "traceroute inet6 routing-instance {vrf} {target} wait 1 source {source}" + bgp_route: StrictStr = "show route protocol bgp table {vrf} {target} detail" + bgp_aspath: StrictStr = "show route protocol bgp table {vrf} aspath-regex {target}" + bgp_community: StrictStr = "show route protocol bgp table {vrf} community {target}" + ping: StrictStr = "ping inet6 routing-instance {vrf} {target} count 5 source {source}" + traceroute: StrictStr = "traceroute inet6 routing-instance {vrf} {target} wait 1 source {source}" ipv4_default: IPv4 = IPv4() ipv6_default: IPv6 = IPv6() @@ -205,43 +211,43 @@ class Commands(HyperglassModel): ipv6_vpn: VPNIPv6 = VPNIPv6() class Huawei(Command): - """Class model for default huawei commands""" + """Validation model for default huawei commands.""" class IPv4(Command.IPv4): - """Default commands for ipv4 commands""" + """Default commands for ipv4 commands.""" - bgp_community: str = "display bgp routing-table regular-expression {target}" - bgp_aspath: str = "display bgp routing-table regular-expression {target}" - bgp_route: str = "display bgp routing-table {target}" - ping: str = "ping -c 5 -a {source} {target}" - traceroute: str = "tracert -q 2 -f 1 -a {source} {target}" + bgp_community: StrictStr = "display bgp routing-table regular-expression {target}" + bgp_aspath: StrictStr = "display bgp routing-table regular-expression {target}" + bgp_route: StrictStr = "display bgp routing-table {target}" + ping: StrictStr = "ping -c 5 -a {source} {target}" + traceroute: StrictStr = "tracert -q 2 -f 1 -a {source} {target}" class IPv6(Command.IPv6): - """Default commands for ipv6 commands""" + """Default commands for ipv6 commands.""" - bgp_community: str = "display bgp ipv6 routing-table community {target}" - bgp_aspath: str = "display bgp ipv6 routing-table regular-expression {target}" - bgp_route: str = "display bgp ipv6 routing-table {target}" - ping: str = "ping ipv6 -c 5 -a {source} {target}" - traceroute: str = "tracert ipv6 -q 2 -f 1 -a {source} {target}" + bgp_community: StrictStr = "display bgp ipv6 routing-table community {target}" + bgp_aspath: StrictStr = "display bgp ipv6 routing-table regular-expression {target}" + bgp_route: StrictStr = "display bgp ipv6 routing-table {target}" + ping: StrictStr = "ping ipv6 -c 5 -a {source} {target}" + traceroute: StrictStr = "tracert ipv6 -q 2 -f 1 -a {source} {target}" class VPNIPv4(Command.VPNIPv4): - """Default commands for dual afi commands""" + """Default commands for dual afi commands.""" - bgp_community: str = "display bgp vpnv4 vpn-instance {vrf} routing-table regular-expression {target}" - bgp_aspath: str = "display bgp vpnv4 vpn-instance {vrf} routing-table regular-expression {target}" - bgp_route: str = "display bgp vpnv4 vpn-instance {vrf} routing-table {target}" - ping: str = "ping -vpn-instance {vrf} -c 5 -a {source} {target}" - traceroute: str = "tracert -q 2 -f 1 -vpn-instance {vrf} -a {source} {target}" + bgp_community: StrictStr = "display bgp vpnv4 vpn-instance {vrf} routing-table regular-expression {target}" + bgp_aspath: StrictStr = "display bgp vpnv4 vpn-instance {vrf} routing-table regular-expression {target}" + bgp_route: StrictStr = "display bgp vpnv4 vpn-instance {vrf} routing-table {target}" + ping: StrictStr = "ping -vpn-instance {vrf} -c 5 -a {source} {target}" + traceroute: StrictStr = "tracert -q 2 -f 1 -vpn-instance {vrf} -a {source} {target}" class VPNIPv6(Command.VPNIPv6): - """Default commands for dual afi commands""" + """Default commands for dual afi commands.""" - bgp_community: str = "display bgp vpnv6 vpn-instance {vrf} routing-table regular-expression {target}" - bgp_aspath: str = "display bgp vpnv6 vpn-instance {vrf} routing-table regular-expression {target}" - bgp_route: str = "display bgp vpnv6 vpn-instance {vrf} routing-table {target}" - ping: str = "ping vpnv6 vpn-instance {vrf} -c 5 -a {source} {target}" - traceroute: str = "tracert -q 2 -f 1 vpn-instance {vrf} -a {source} {target}" + bgp_community: StrictStr = "display bgp vpnv6 vpn-instance {vrf} routing-table regular-expression {target}" + bgp_aspath: StrictStr = "display bgp vpnv6 vpn-instance {vrf} routing-table regular-expression {target}" + bgp_route: StrictStr = "display bgp vpnv6 vpn-instance {vrf} routing-table {target}" + ping: StrictStr = "ping vpnv6 vpn-instance {vrf} -c 5 -a {source} {target}" + traceroute: StrictStr = "tracert -q 2 -f 1 vpn-instance {vrf} -a {source} {target}" ipv4_default: IPv4 = IPv4() ipv6_default: IPv6 = IPv6() @@ -249,43 +255,43 @@ class Commands(HyperglassModel): ipv6_vpn: VPNIPv6 = VPNIPv6() class Arista(Command): - """Class model for non-default commands""" + """Validation model for non-default commands.""" class IPv4(Command.IPv4): - """Class model for non-default dual afi commands""" + """Validation model for non-default dual afi commands.""" - bgp_route: str = "show ip bgp {target}" - bgp_aspath: str = "show ip bgp regexp {target}" - bgp_community: str = "show ip bgp community {target}" - ping: str = "ping ip {target} source {source}" - traceroute: str = "traceroute ip {target} source {source}" + bgp_route: StrictStr = "show ip bgp {target}" + bgp_aspath: StrictStr = "show ip bgp regexp {target}" + bgp_community: StrictStr = "show ip bgp community {target}" + ping: StrictStr = "ping ip {target} source {source}" + traceroute: StrictStr = "traceroute ip {target} source {source}" class IPv6(Command.IPv6): - """Class model for non-default ipv4 commands""" + """Validation model for non-default ipv4 commands.""" - bgp_route: str = "show ipv6 bgp {target}" - bgp_aspath: str = "show ipv6 bgp regexp {target}" - bgp_community: str = "show ipv6 bgp community {target}" - ping: str = "ping ipv6 {target} source {source}" - traceroute: str = "traceroute ipv6 {target} source {source}" + bgp_route: StrictStr = "show ipv6 bgp {target}" + bgp_aspath: StrictStr = "show ipv6 bgp regexp {target}" + bgp_community: StrictStr = "show ipv6 bgp community {target}" + ping: StrictStr = "ping ipv6 {target} source {source}" + traceroute: StrictStr = "traceroute ipv6 {target} source {source}" class VPNIPv4(Command.VPNIPv4): - """Class model for non-default ipv6 commands""" + """Validation model for non-default ipv6 commands.""" - bgp_route: str = "show ip bgp {target} vrf {vrf}" - bgp_aspath: str = "show ip bgp regexp {target} vrf {vrf}" - bgp_community: str = "show ip bgp community {target} vrf {vrf}" - ping: str = "ping vrf {vrf} ip {target} source {source}" - traceroute: str = "traceroute vrf {vrf} ip {target} source {source}" + bgp_route: StrictStr = "show ip bgp {target} vrf {vrf}" + bgp_aspath: StrictStr = "show ip bgp regexp {target} vrf {vrf}" + bgp_community: StrictStr = "show ip bgp community {target} vrf {vrf}" + ping: StrictStr = "ping vrf {vrf} ip {target} source {source}" + traceroute: StrictStr = "traceroute vrf {vrf} ip {target} source {source}" class VPNIPv6(Command.VPNIPv6): - """Class model for non-default ipv6 commands""" + """Validation model for non-default ipv6 commands.""" - bgp_route: str = "show ipv6 bgp {target} vrf {vrf}" - bgp_aspath: str = "show ipv6 bgp regexp {target} vrf {vrf}" - bgp_community: str = "show ipv6 bgp community {target} vrf {vrf}" - ping: str = "ping vrf {vrf} ipv6 {target} source {source}" - traceroute: str = "traceroute vrf {vrf} ipv6 {target} source {source}" + bgp_route: StrictStr = "show ipv6 bgp {target} vrf {vrf}" + bgp_aspath: StrictStr = "show ipv6 bgp regexp {target} vrf {vrf}" + bgp_community: StrictStr = "show ipv6 bgp community {target} vrf {vrf}" + ping: StrictStr = "ping vrf {vrf} ipv6 {target} source {source}" + traceroute: StrictStr = "traceroute vrf {vrf} ipv6 {target} source {source}" ipv4_default: IPv4 = IPv4() ipv6_default: IPv6 = IPv6() @@ -299,6 +305,6 @@ class Commands(HyperglassModel): arista: Command = Arista() class Config: - """Pydantic Config Overrides""" + """Override pydantic config.""" validate_all = False diff --git a/hyperglass/configuration/models/credentials.py b/hyperglass/configuration/models/credentials.py index abe22f7..0303787 100644 --- a/hyperglass/configuration/models/credentials.py +++ b/hyperglass/configuration/models/credentials.py @@ -20,10 +20,13 @@ class Credentials(HyperglassModel): @classmethod def import_params(cls, input_params): - """ - Imports passed dict from YAML config, removes unsupported - characters from device names, dynamically sets attributes for - the credentials class. + """Import credentials with corrected field names. + + Arguments: + input_params {dict} -- Credential definition + + Returns: + {object} -- Validated credential object """ obj = Credentials() for (credname, params) in input_params.items(): diff --git a/hyperglass/configuration/models/features.py b/hyperglass/configuration/models/features.py index a8ba358..4f951e6 100644 --- a/hyperglass/configuration/models/features.py +++ b/hyperglass/configuration/models/features.py @@ -4,6 +4,9 @@ from math import ceil # Third Party Imports +from pydantic import StrictBool +from pydantic import StrictInt +from pydantic import StrictStr from pydantic import constr # Project Imports @@ -16,33 +19,33 @@ class Features(HyperglassModel): class BgpRoute(HyperglassModel): """Validation model for params.features.bgp_route.""" - enable: bool = True + enable: StrictBool = True class BgpCommunity(HyperglassModel): """Validation model for params.features.bgp_community.""" - enable: bool = True + enable: StrictBool = True class Regex(HyperglassModel): """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})$" - large: str = r"^([0-9]{1,10})\:([0-9]{1,10})\:[0-9]{1,10}$" + 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}$" regex: Regex = Regex() class BgpAsPath(HyperglassModel): """Validation model for params.features.bgp_aspath.""" - enable: bool = True + enable: StrictBool = True class Regex(HyperglassModel): """Validation model for params.bgp_aspath.regex.""" mode: constr(regex="asplain|asdot") = "asplain" - asplain: str = r"^(\^|^\_)(\d+\_|\d+\$|\d+\(\_\.\+\_\))+$" - asdot: str = ( + asplain: StrictStr = r"^(\^|^\_)(\d+\_|\d+\$|\d+\(\_\.\+\_\))+$" + asdot: StrictStr = ( r"^(\^|^\_)((\d+\.\d+)\_|(\d+\.\d+)\$|(\d+\.\d+)\(\_\.\+\_\))+$" ) @@ -51,61 +54,61 @@ class Features(HyperglassModel): class Ping(HyperglassModel): """Validation model for params.features.ping.""" - enable: bool = True + enable: StrictBool = True class Traceroute(HyperglassModel): """Validation model for params.features.traceroute.""" - enable: bool = True + enable: StrictBool = True class Cache(HyperglassModel): """Validation model for params.features.cache.""" - redis_id: int = 0 - timeout: int = 120 - show_text: bool = True - text: str = "Results will be cached for {timeout} minutes.".format( + redis_id: StrictInt = 0 + timeout: StrictInt = 120 + show_text: StrictBool = True + text: StrictStr = "Results will be cached for {timeout} minutes.".format( timeout=ceil(timeout / 60) ) class MaxPrefix(HyperglassModel): """Validation model for params.features.max_prefix.""" - enable: bool = False - ipv4: int = 24 - ipv6: int = 64 - message: str = ( + enable: StrictBool = False + ipv4: StrictInt = 24 + ipv6: StrictInt = 64 + message: StrictStr = ( "Prefix length must be smaller than /{m}. {i} is too specific." ) class RateLimit(HyperglassModel): """Validation model for params.features.rate_limit.""" - redis_id: int = 1 + redis_id: StrictInt = 1 class Query(HyperglassModel): """Validation model for params.features.rate_limit.query.""" - rate: int = 5 - period: str = "minute" - title: str = "Query Limit Reached" - message: str = ( + rate: StrictInt = 5 + period: StrictStr = "minute" + title: StrictStr = "Query Limit Reached" + message: StrictStr = ( "Query limit of {rate} per {period} reached. " "Please wait one minute and try again." ).format(rate=rate, period=period) - button: str = "Try Again" + button: StrictStr = "Try Again" class Site(HyperglassModel): """Validation model for params.features.rate_limit.site.""" - rate: int = 60 - period: str = "minute" - title: str = "Limit Reached" - subtitle: str = ( + rate: StrictInt = 60 + period: StrictStr = "minute" + title: StrictStr = "Limit Reached" + subtitle: StrictStr = ( "You have accessed this site more than {rate} " "times in the last {period}." ).format(rate=rate, period=period) - button: str = "Try Again" + button: StrictStr = "Try Again" query: Query = Query() site: Site = Site() diff --git a/hyperglass/configuration/models/messages.py b/hyperglass/configuration/models/messages.py index b98a079..0ffe9cb 100644 --- a/hyperglass/configuration/models/messages.py +++ b/hyperglass/configuration/models/messages.py @@ -1,5 +1,8 @@ """Validate error message configuration variables.""" +# Third Party Imports +from pydantic import StrictStr + # Project Imports from hyperglass.configuration.models._utils import HyperglassModel @@ -7,24 +10,24 @@ from hyperglass.configuration.models._utils import HyperglassModel class Messages(HyperglassModel): """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." - acl_not_allowed: str = "{target} is not allowed." - max_prefix: str = ( + 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." ) - requires_ipv6_cidr: str = ( + requires_ipv6_cidr: StrictStr = ( "{device_name} requires IPv6 BGP lookups to be in CIDR notation." ) - feature_not_enabled: str = "{feature} is not enabled for {device_name}." - invalid_input: str = "{target} is not a valid {query_type} target." - invalid_field: str = "{input} is an invalid {field}." - general: str = "Something went wrong." - directed_cidr: str = "{query_type} queries can not be in CIDR format." - request_timeout: str = "Request timed out." - connection_error: str = "Error connecting to {device_name}: {error}" - authentication_error: str = "Authentication error occurred." - noresponse_error: str = "No response." - vrf_not_associated: str = "VRF {vrf_name} is not associated with {device_name}." - no_matching_vrfs: str = "No VRFs in Common" - no_output: str = "No output." + 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}." + no_matching_vrfs: StrictStr = "No VRFs in Common" + no_output: StrictStr = "No output." diff --git a/hyperglass/configuration/models/networks.py b/hyperglass/configuration/models/networks.py index 5d74981..54636e3 100644 --- a/hyperglass/configuration/models/networks.py +++ b/hyperglass/configuration/models/networks.py @@ -1,5 +1,8 @@ """Validate network configuration variables.""" +# Third Party Imports +from pydantic import StrictStr + # Project Imports from hyperglass.configuration.models._utils import HyperglassModel from hyperglass.configuration.models._utils import clean_name @@ -8,8 +11,8 @@ from hyperglass.configuration.models._utils import clean_name class Network(HyperglassModel): """Validation Model for per-network/asn config in devices.yaml.""" - name: str - display_name: str + name: StrictStr + display_name: StrictStr class Networks(HyperglassModel): @@ -17,10 +20,17 @@ class Networks(HyperglassModel): @classmethod def import_params(cls, input_params): - """ - Imports passed dict from YAML config, removes unsupported - characters from device names, dynamically sets attributes for - the credentials class. + """Import loaded YAML, initialize per-network definitions. + + Remove unsupported characters from network names, dynamically + set attributes for the networks class. Add cls.networks + attribute so network objects can be accessed inside a dict. + + Arguments: + input_params {dict} -- Unvalidated network definitions + + Returns: + {object} -- Validated networks object """ obj = Networks() networks = {} diff --git a/hyperglass/configuration/models/proxies.py b/hyperglass/configuration/models/proxies.py index d50f6b0..f68f116 100644 --- a/hyperglass/configuration/models/proxies.py +++ b/hyperglass/configuration/models/proxies.py @@ -1,6 +1,8 @@ """Validate SSH proxy configuration variables.""" # Third Party Imports +from pydantic import StrictInt +from pydantic import StrictStr from pydantic import validator # Project Imports @@ -13,16 +15,21 @@ from hyperglass.exceptions import UnsupportedDevice class Proxy(HyperglassModel): """Validation model for per-proxy config in devices.yaml.""" - name: str - address: str - port: int = 22 + name: StrictStr + address: StrictStr + port: StrictInt = 22 credential: Credential - nos: str = "linux_ssh" + nos: StrictStr = "linux_ssh" @validator("nos") - def supported_nos(cls, value): # noqa: N805 - """ - Validates that passed nos string is supported by hyperglass. + def supported_nos(cls, value): + """Verify NOS is supported by hyperglass. + + Raises: + UnsupportedDevice: Raised if NOS is not supported. + + Returns: + {str} -- Valid NOS name """ if not value == "linux_ssh": raise UnsupportedDevice(f'"{value}" device type is not supported.') @@ -34,10 +41,16 @@ class Proxies(HyperglassModel): @classmethod def import_params(cls, input_params): - """ - Imports passed dict from YAML config, removes unsupported - characters from device names, dynamically sets attributes for - the proxies class. + """Import loaded YAML, initialize per-proxy definitions. + + Remove unsupported characters from proxy names, dynamically + set attributes for the proxies class. + + Arguments: + input_params {dict} -- Unvalidated proxy definitions + + Returns: + {object} -- Validated proxies object """ obj = Proxies() for (devname, params) in input_params.items(): diff --git a/hyperglass/configuration/models/routers.py b/hyperglass/configuration/models/routers.py index 793d93f..868b109 100644 --- a/hyperglass/configuration/models/routers.py +++ b/hyperglass/configuration/models/routers.py @@ -6,6 +6,8 @@ from typing import List from typing import Union # Third Party Imports +from pydantic import StrictInt +from pydantic import StrictStr from pydantic import validator # Project Imports @@ -27,46 +29,65 @@ from hyperglass.util import log class Router(HyperglassModel): """Validation model for per-router config in devices.yaml.""" - name: str - address: str + name: StrictStr + address: StrictStr network: Network credential: Credential proxy: Union[Proxy, None] = None - location: str - display_name: str - port: int - nos: str + location: StrictStr + display_name: StrictStr + port: StrictInt + nos: StrictStr commands: Union[Command, None] = None vrfs: List[Vrf] = [DefaultVrf()] - display_vrfs: List[str] = [] - vrf_names: List[str] = [] + display_vrfs: List[StrictStr] = [] + vrf_names: List[StrictStr] = [] @validator("nos") - def supported_nos(cls, v): # noqa: N805 + def supported_nos(cls, value): + """Validate that nos is supported by hyperglass. + + Raises: + UnsupportedDevice: Raised if nos is unsupported. + + Returns: + {str} -- Valid NOS """ - Validates that passed nos string is supported by hyperglass. - """ - if not Supported.is_supported(v): - raise UnsupportedDevice(f'"{v}" device type is not supported.') - return v + if not Supported.is_supported(value): + raise UnsupportedDevice(f'"{value}" device type is not supported.') + return value @validator("name", "location") - def clean_name(cls, v): # noqa: N805 - """Remove or replace unsupported characters from field values""" - return clean_name(v) + def clean_name(cls, value): + """Remove or replace unsupported characters from field values. + + Arguments: + value {str} -- Raw name/location + + Returns: + {} -- Valid name/location + """ + return clean_name(value) @validator("commands", always=True) - def validate_commands(cls, v, values): # noqa: N805 + def validate_commands(cls, value, values): + """If a named command profile is not defined, use the NOS name. + + Arguments: + value {str} -- Reference to command profile + values {dict} -- Other already-validated fields + + Returns: + {str} -- Command profile or NOS name """ - If a named command profile is not defined, use the NOS name. - """ - if v is None: - v = values["nos"] - return v + if value is None: + value = values["nos"] + return value @validator("vrfs", pre=True) def validate_vrfs(cls, value, values): - """ + """Validate VRF definitions. + - Ensures source IP addresses are set for the default VRF (global routing table). - Initializes the default VRF with the DefaultVRF() class so @@ -74,6 +95,16 @@ class Router(HyperglassModel): table. - If the 'display_name' is not set for a non-default VRF, try to make one that looks pretty based on the 'name'. + + Arguments: + value {list} -- List of VRFs + values {dict} -- Other already-validated fields + + Raises: + ConfigError: Raised if the VRF is missing a source address + + Returns: + {list} -- List of valid VRFs """ vrfs = [] for vrf in value: @@ -101,7 +132,9 @@ class Router(HyperglassModel): # class. (See vrfs.py) vrf = DefaultVrf(**vrf) - elif vrf_name != "default" and not isinstance(vrf.get("display_name"), str): + elif vrf_name != "default" and not isinstance( + vrf.get("display_name"), StrictStr + ): # If no display_name is set for a non-default VRF, try # to make one by replacing non-alphanumeric characters @@ -128,25 +161,32 @@ class Router(HyperglassModel): class Routers(HyperglassModelExtra): """Validation model for device configurations.""" - hostnames: List[str] = [] - vrfs: List[str] = [] - display_vrfs: List[str] = [] + hostnames: List[StrictStr] = [] + vrfs: List[StrictStr] = [] + display_vrfs: List[StrictStr] = [] routers: List[Router] = [] @classmethod def _import(cls, input_params): - """ - Imports passed list of dictionaries from YAML config, validates - each router config, sets class attributes for each router for - easy access. Also builds lists of common attributes for easy - access in other modules. + """Import loaded YAML, initialize per-network definitions. + + Remove unsupported characters from device names, dynamically + set attributes for the devices class. Builds lists of common + attributes for easy access in other modules. + + Arguments: + input_params {dict} -- Unvalidated router definitions + + Returns: + {object} -- Validated routers object """ vrfs = set() display_vrfs = set() - setattr(cls, "routers", []) - setattr(cls, "hostnames", []) - setattr(cls, "vrfs", []) - setattr(cls, "display_vrfs", []) + routers = Routers() + routers.routers = [] + routers.hostnames = [] + routers.vrfs = [] + routers.display_vrfs = [] for definition in input_params: # Validate each router config against Router() model/schema @@ -154,14 +194,14 @@ class Routers(HyperglassModelExtra): # Set a class attribute for each router so each router's # attributes can be accessed with `devices.router_hostname` - setattr(cls, router.name, router) + setattr(routers, router.name, router) # Add router-level attributes (assumed to be unique) to # class lists, e.g. so all hostnames can be accessed as a # list with `devices.hostnames`, same for all router # classes, for when iteration over all routers is required. - cls.hostnames.append(router.name) - cls.routers.append(router) + routers.hostnames.append(router.name) + routers.routers.append(router) for vrf in router.vrfs: # For each configured router VRF, add its name and @@ -177,15 +217,14 @@ class Routers(HyperglassModelExtra): # Add a 'default_vrf' attribute to the devices class # which contains the configured default VRF display name if vrf.name == "default" and not hasattr(cls, "default_vrf"): - setattr( - cls, - "default_vrf", - {"name": vrf.name, "display_name": vrf.display_name}, - ) + routers.default_vrf = { + "name": vrf.name, + "display_name": vrf.display_name, + } # Convert the de-duplicated sets to a standard list, add lists # as class attributes - setattr(cls, "vrfs", list(vrfs)) - setattr(cls, "display_vrfs", list(display_vrfs)) + routers.vrfs = list(vrfs) + routers.display_vrfs = list(display_vrfs) - return cls + return routers diff --git a/hyperglass/configuration/models/vrfs.py b/hyperglass/configuration/models/vrfs.py index 36742ed..1105711 100644 --- a/hyperglass/configuration/models/vrfs.py +++ b/hyperglass/configuration/models/vrfs.py @@ -11,55 +11,27 @@ from typing import Optional # Third Party Imports from pydantic import IPvAnyNetwork +from pydantic import StrictStr from pydantic import constr from pydantic import validator # Project Imports from hyperglass.configuration.models._utils import HyperglassModel -from hyperglass.exceptions import ConfigError class DeviceVrf4(HyperglassModel): """Validation model for IPv4 AFI definitions.""" - vrf_name: str + vrf_name: StrictStr source_address: IPv4Address - @validator("source_address") - def check_ip_type(cls, value, values): - if value is not None and isinstance(value, IPv4Address): - if value.is_loopback: - raise ConfigError( - ( - "The default routing table with source IPs must be defined. " - "VRF: {vrf}, Source Address: {value}" - ), - vrf=values["vrf_name"], - value=value, - ) - return value - class DeviceVrf6(HyperglassModel): """Validation model for IPv6 AFI definitions.""" - vrf_name: str + vrf_name: StrictStr source_address: IPv6Address - @validator("source_address") - def check_ip_type(cls, value, values): - if value is not None and isinstance(value, IPv4Address): - if value.is_loopback: - raise ConfigError( - ( - "The default routing table with source IPs must be defined. " - "VRF: {vrf}, Source Address: {value}" - ), - vrf=values["vrf_name"], - value=value, - ) - return value - class Vrf(HyperglassModel): """Validation model for per VRF/afi config in devices.yaml.""" @@ -75,6 +47,11 @@ class Vrf(HyperglassModel): @validator("ipv4", "ipv6", pre=True, always=True) def set_default_vrf_name(cls, value, values): + """If per-AFI name is undefined, set it to the global VRF name. + + Returns: + {str} -- VRF Name + """ if isinstance(value, DefaultVrf) and value.vrf_name is None: value["vrf_name"] = values["name"] elif isinstance(value, Dict) and value.get("vrf_name") is None: @@ -83,6 +60,11 @@ class Vrf(HyperglassModel): @validator("access_list", pre=True) def validate_action(cls, value): + """Transform ACL networks to IPv4Network/IPv6Network objects. + + Returns: + {object} -- IPv4Network/IPv6Network object + """ for li in value: for action, network in li.items(): if isinstance(network, (IPv4Network, IPv6Network)): @@ -93,8 +75,8 @@ class Vrf(HyperglassModel): class DefaultVrf(HyperglassModel): """Validation model for default routing table VRF.""" - name: str = "default" - display_name: str = "Global" + name: StrictStr = "default" + display_name: StrictStr = "Global" access_list: List[Dict[constr(regex=("allow|deny")), IPvAnyNetwork]] = [ {"allow": IPv4Network("0.0.0.0/0")}, {"allow": IPv6Network("::/0")}, @@ -103,14 +85,14 @@ class DefaultVrf(HyperglassModel): class DefaultVrf4(HyperglassModel): """Validation model for IPv4 default routing table VRF definition.""" - vrf_name: str = "default" - source_address: IPv4Address = IPv4Address("127.0.0.1") + vrf_name: StrictStr = "default" + source_address: IPv4Address class DefaultVrf6(HyperglassModel): """Validation model for IPv6 default routing table VRF definition.""" - vrf_name: str = "default" - source_address: IPv6Address = IPv6Address("::1") + vrf_name: StrictStr = "default" + source_address: IPv6Address - ipv4: DefaultVrf4 = DefaultVrf4() - ipv6: DefaultVrf6 = DefaultVrf6() + ipv4: Optional[DefaultVrf4] + ipv6: Optional[DefaultVrf6] diff --git a/line_count.svg b/line_count.svg index 406c720..2178915 100644 --- a/line_count.svg +++ b/line_count.svg @@ -17,7 +17,7 @@ Lines of Code - 3173 - 3173 + 3202 + 3202 \ No newline at end of file