mirror of
https://github.com/checktheroads/hyperglass
synced 2024-05-11 05:55:08 +00:00
remove requirement for default VRF to be named default, closes #29
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
import os
|
||||
import copy
|
||||
import json
|
||||
from typing import Dict
|
||||
from typing import Dict, List
|
||||
from pathlib import Path
|
||||
|
||||
# Third Party
|
||||
@@ -220,6 +220,7 @@ def _build_frontend_devices():
|
||||
{
|
||||
"id": vrf.name,
|
||||
"display_name": vrf.display_name,
|
||||
"default": vrf.default,
|
||||
"ipv4": True if vrf.ipv4 else False, # noqa: IF100
|
||||
"ipv6": True if vrf.ipv6 else False, # noqa: IF100
|
||||
}
|
||||
@@ -235,6 +236,7 @@ def _build_frontend_devices():
|
||||
{
|
||||
"id": vrf.name,
|
||||
"display_name": vrf.display_name,
|
||||
"default": vrf.default,
|
||||
"ipv4": True if vrf.ipv4 else False, # noqa: IF100
|
||||
"ipv6": True if vrf.ipv6 else False, # noqa: IF100
|
||||
}
|
||||
@@ -246,15 +248,8 @@ def _build_frontend_devices():
|
||||
return frontend_dict
|
||||
|
||||
|
||||
def _build_networks():
|
||||
"""Build filtered JSON Structure of networks & devices for Jinja templates.
|
||||
|
||||
Raises:
|
||||
ConfigError: Raised if parsing/building error occurs.
|
||||
|
||||
Returns:
|
||||
{dict} -- Networks & devices
|
||||
"""
|
||||
def _build_networks() -> List[Dict]:
|
||||
"""Build filtered JSON Structure of networks & devices for Jinja templates."""
|
||||
networks = []
|
||||
_networks = list(set({device.network.display_name for device in devices.objects}))
|
||||
|
||||
@@ -269,8 +264,9 @@ def _build_networks():
|
||||
"network": device.network.display_name,
|
||||
"vrfs": [
|
||||
{
|
||||
"id": vrf.name,
|
||||
"_id": vrf._id,
|
||||
"display_name": vrf.display_name,
|
||||
"default": vrf.default,
|
||||
"ipv4": True if vrf.ipv4 else False, # noqa: IF100
|
||||
"ipv6": True if vrf.ipv6 else False, # noqa: IF100
|
||||
}
|
||||
@@ -285,33 +281,13 @@ def _build_networks():
|
||||
return networks
|
||||
|
||||
|
||||
def _build_vrfs():
|
||||
vrfs = []
|
||||
for device in devices.objects:
|
||||
for vrf in device.vrfs:
|
||||
|
||||
vrf_dict = {
|
||||
"id": vrf.name,
|
||||
"display_name": vrf.display_name,
|
||||
}
|
||||
|
||||
if vrf_dict not in vrfs:
|
||||
vrfs.append(vrf_dict)
|
||||
|
||||
return vrfs
|
||||
|
||||
|
||||
content_params = json.loads(
|
||||
params.json(include={"primary_asn", "org_name", "site_title", "site_description"})
|
||||
)
|
||||
|
||||
|
||||
def _build_vrf_help():
|
||||
"""Build a dict of vrfs as keys, help content as values.
|
||||
|
||||
Returns:
|
||||
{dict} -- Formatted VRF help
|
||||
"""
|
||||
def _build_vrf_help() -> Dict:
|
||||
"""Build a dict of vrfs as keys, help content as values."""
|
||||
all_help = {}
|
||||
for vrf in devices.vrf_objects:
|
||||
|
||||
@@ -343,7 +319,7 @@ def _build_vrf_help():
|
||||
}
|
||||
)
|
||||
|
||||
all_help.update({vrf.name: vrf_help})
|
||||
all_help.update({vrf._id: vrf_help})
|
||||
|
||||
return all_help
|
||||
|
||||
@@ -369,7 +345,6 @@ content_terms = get_markdown(
|
||||
)
|
||||
content_credit = CREDIT.format(version=__version__)
|
||||
|
||||
vrfs = _build_vrfs()
|
||||
networks = _build_networks()
|
||||
frontend_devices = _build_frontend_devices()
|
||||
_include_fields = {
|
||||
@@ -397,7 +372,6 @@ _frontend_params.update(
|
||||
"hyperglass_version": __version__,
|
||||
"queries": {**params.queries.map, "list": params.queries.list},
|
||||
"networks": networks,
|
||||
"vrfs": vrfs,
|
||||
"parsed_data_fields": PARSED_RESPONSE_FIELDS,
|
||||
"content": {
|
||||
"help_menu": content_help,
|
||||
|
@@ -21,33 +21,24 @@ from .validators import (
|
||||
validate_community_input,
|
||||
validate_community_select,
|
||||
)
|
||||
from ..config.vrf import Vrf
|
||||
|
||||
|
||||
def get_vrf_object(vrf_name):
|
||||
"""Match VRF object from VRF name.
|
||||
def get_vrf_object(vrf_name: str) -> Vrf:
|
||||
"""Match VRF object from VRF name."""
|
||||
|
||||
Arguments:
|
||||
vrf_name {str} -- VRF name
|
||||
|
||||
Raises:
|
||||
InputInvalid: Raised if no VRF is matched.
|
||||
|
||||
Returns:
|
||||
{object} -- Valid VRF object
|
||||
"""
|
||||
matched = None
|
||||
for vrf_obj in devices.vrf_objects:
|
||||
if vrf_name is not None:
|
||||
if vrf_name == vrf_obj.name or vrf_name == vrf_obj.display_name:
|
||||
matched = vrf_obj
|
||||
break
|
||||
if vrf_name == vrf_obj._id or vrf_name == vrf_obj.display_name:
|
||||
return vrf_obj
|
||||
|
||||
elif vrf_name == "__hyperglass_default" and vrf_obj.default:
|
||||
return vrf_obj
|
||||
elif vrf_name is None:
|
||||
if vrf_obj.name == "default":
|
||||
matched = vrf_obj
|
||||
break
|
||||
if matched is None:
|
||||
raise InputInvalid(params.messages.vrf_not_found, vrf_name=vrf_name)
|
||||
return matched
|
||||
if vrf_obj.default:
|
||||
return vrf_obj
|
||||
|
||||
raise InputInvalid(params.messages.vrf_not_found, vrf_name=vrf_name)
|
||||
|
||||
|
||||
class Query(BaseModel):
|
||||
@@ -145,7 +136,7 @@ class Query(BaseModel):
|
||||
items = {
|
||||
"query_location": self.query_location,
|
||||
"query_type": self.query_type,
|
||||
"query_vrf": self.query_vrf.name,
|
||||
"query_vrf": self.query_vrf._id,
|
||||
"query_target": str(self.query_target),
|
||||
}
|
||||
return items
|
||||
@@ -156,17 +147,7 @@ class Query(BaseModel):
|
||||
|
||||
@validator("query_type")
|
||||
def validate_query_type(cls, value):
|
||||
"""Ensure query_type is enabled.
|
||||
|
||||
Arguments:
|
||||
value {str} -- Query Type
|
||||
|
||||
Raises:
|
||||
InputInvalid: Raised if query_type is disabled.
|
||||
|
||||
Returns:
|
||||
{str} -- Valid query_type
|
||||
"""
|
||||
"""Ensure query_type is enabled."""
|
||||
query = params.queries[value]
|
||||
if not query.enable:
|
||||
raise InputInvalid(
|
||||
@@ -178,17 +159,7 @@ class Query(BaseModel):
|
||||
|
||||
@validator("query_location")
|
||||
def validate_query_location(cls, value):
|
||||
"""Ensure query_location is defined.
|
||||
|
||||
Arguments:
|
||||
value {str} -- Unvalidated query_location
|
||||
|
||||
Raises:
|
||||
InputInvalid: Raised if query_location is not defined.
|
||||
|
||||
Returns:
|
||||
{str} -- Valid query_location
|
||||
"""
|
||||
"""Ensure query_location is defined."""
|
||||
if value not in devices._ids:
|
||||
raise InputInvalid(
|
||||
params.messages.invalid_field,
|
||||
@@ -200,17 +171,7 @@ class Query(BaseModel):
|
||||
|
||||
@validator("query_vrf")
|
||||
def validate_query_vrf(cls, value, values):
|
||||
"""Ensure query_vrf is defined.
|
||||
|
||||
Arguments:
|
||||
value {str} -- Unvalidated query_vrf
|
||||
|
||||
Raises:
|
||||
InputInvalid: Raised if query_vrf is not defined.
|
||||
|
||||
Returns:
|
||||
{str} -- Valid query_vrf
|
||||
"""
|
||||
"""Ensure query_vrf is defined."""
|
||||
vrf_object = get_vrf_object(value)
|
||||
device = devices[values["query_location"]]
|
||||
device_vrf = None
|
||||
|
@@ -237,7 +237,7 @@ class Device(HyperglassModel):
|
||||
"""
|
||||
vrfs = []
|
||||
for vrf in value:
|
||||
vrf_name = vrf.get("name")
|
||||
vrf_default = vrf.get("default", False)
|
||||
|
||||
for afi in ("ipv4", "ipv6"):
|
||||
vrf_afi = vrf.get(afi)
|
||||
@@ -259,9 +259,7 @@ class Device(HyperglassModel):
|
||||
# to make one by replacing non-alphanumeric characters
|
||||
# with whitespaces and using str.title() to make each
|
||||
# word look "pretty".
|
||||
if vrf_name != "default" and not isinstance(
|
||||
vrf.get("display_name"), StrictStr
|
||||
):
|
||||
if not vrf_default and not isinstance(vrf.get("display_name"), str):
|
||||
new_name = vrf["name"]
|
||||
new_name = re.sub(r"[^a-zA-Z0-9]", " ", new_name)
|
||||
new_name = re.split(" ", new_name)
|
||||
@@ -272,7 +270,7 @@ class Device(HyperglassModel):
|
||||
f"Generated '{vrf['display_name']}'"
|
||||
)
|
||||
|
||||
elif vrf_name == "default" and vrf.get("display_name") is None:
|
||||
elif vrf_default and vrf.get("display_name") is None:
|
||||
vrf["display_name"] = "Global"
|
||||
|
||||
# Validate the non-default VRF against the standard
|
||||
|
@@ -1,7 +1,8 @@
|
||||
"""Validate VRF configuration variables."""
|
||||
|
||||
# Standard Library
|
||||
from typing import List, Optional
|
||||
import re
|
||||
from typing import Dict, List, Optional
|
||||
from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network
|
||||
|
||||
# Third Party
|
||||
@@ -10,18 +11,39 @@ from pydantic import (
|
||||
FilePath,
|
||||
StrictStr,
|
||||
StrictBool,
|
||||
PrivateAttr,
|
||||
conint,
|
||||
constr,
|
||||
validator,
|
||||
root_validator,
|
||||
)
|
||||
|
||||
# Project
|
||||
from hyperglass.log import log
|
||||
|
||||
# Local
|
||||
from ..main import HyperglassModel, HyperglassModelExtra
|
||||
|
||||
ACLAction = constr(regex=r"permit|deny")
|
||||
|
||||
|
||||
def find_vrf_id(values: Dict) -> str:
|
||||
"""Generate (private) VRF ID."""
|
||||
|
||||
def generate_id(name: str) -> str:
|
||||
scrubbed = re.sub(r"[^A-Za-z0-9\_\-\s]", "", name)
|
||||
return "_".join(scrubbed.split()).lower()
|
||||
|
||||
display_name = values.get("display_name")
|
||||
|
||||
if display_name is None:
|
||||
raise ValueError("display_name is required.")
|
||||
|
||||
vrf_id = generate_id(display_name)
|
||||
|
||||
return vrf_id
|
||||
|
||||
|
||||
class AccessList4(HyperglassModel):
|
||||
"""Validation model for IPv4 access-lists."""
|
||||
|
||||
@@ -195,23 +217,33 @@ class DeviceVrf6(HyperglassModelExtra):
|
||||
class Vrf(HyperglassModel):
|
||||
"""Validation model for per VRF/afi config in devices.yaml."""
|
||||
|
||||
_id: StrictStr = PrivateAttr()
|
||||
name: StrictStr
|
||||
display_name: StrictStr
|
||||
info: Info = Info()
|
||||
ipv4: Optional[DeviceVrf4]
|
||||
ipv6: Optional[DeviceVrf6]
|
||||
default: StrictBool = False
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
"""Set the VRF ID."""
|
||||
_id = find_vrf_id(kwargs)
|
||||
super().__init__(**kwargs)
|
||||
self._id = _id
|
||||
|
||||
@root_validator
|
||||
def set_dynamic(cls, values):
|
||||
"""Set dynamic attributes before VRF initialization.
|
||||
def set_dynamic(cls, values: Dict) -> Dict:
|
||||
"""Set dynamic attributes before VRF initialization."""
|
||||
|
||||
Arguments:
|
||||
values {dict} -- Post-validation VRF attributes
|
||||
|
||||
Returns:
|
||||
{dict} -- VRF with new attributes set
|
||||
"""
|
||||
if values["name"] == "default":
|
||||
log.warning(
|
||||
"""You have set the VRF name to 'default'. This is no longer the way to
|
||||
designate a VRF as the default (or global routing table) VRF. Instead,
|
||||
add 'default: true' to the VRF definition.
|
||||
"""
|
||||
)
|
||||
|
||||
if values.get("default", False) is True:
|
||||
protocol4 = "ipv4_default"
|
||||
protocol6 = "ipv6_default"
|
||||
|
||||
@@ -227,7 +259,7 @@ class Vrf(HyperglassModel):
|
||||
values["ipv6"].protocol = protocol6
|
||||
values["ipv6"].version = 6
|
||||
|
||||
if values.get("name") == "default" and values.get("display_name") is None:
|
||||
if values.get("default", False) and values.get("display_name") is None:
|
||||
values["display_name"] = "Global"
|
||||
|
||||
return values
|
||||
@@ -245,7 +277,7 @@ class Vrf(HyperglassModel):
|
||||
{object} -- AFI object
|
||||
"""
|
||||
if i not in (4, 6):
|
||||
raise AttributeError(f"Must be 4 or 6, got '{i}")
|
||||
raise AttributeError(f"Must be 4 or 6, got '{i}'")
|
||||
|
||||
return getattr(self, f"ipv{i}")
|
||||
|
||||
|
@@ -2,11 +2,11 @@ import { useMemo } from 'react';
|
||||
import { Select } from '~/components';
|
||||
import { useLGMethods, useLGState } from '~/hooks';
|
||||
|
||||
import { TDeviceVrf, TSelectOption } from '~/types';
|
||||
import type { TDeviceVrf, TSelectOption } from '~/types';
|
||||
import type { TQueryVrf } from './types';
|
||||
|
||||
function buildOptions(queryVrfs: TDeviceVrf[]): TSelectOption[] {
|
||||
return queryVrfs.map(q => ({ value: q.id, label: q.display_name }));
|
||||
return queryVrfs.map(q => ({ value: q._id, label: q.display_name }));
|
||||
}
|
||||
|
||||
export const QueryVrf: React.FC<TQueryVrf> = (props: TQueryVrf) => {
|
||||
|
@@ -129,17 +129,14 @@ export const LookingGlass: React.FC = () => {
|
||||
// Use _.intersectionWith to create an array of VRFs common to all selected locations.
|
||||
const intersecting = intersectionWith(
|
||||
...allVrfs,
|
||||
(a: TDeviceVrf, b: TDeviceVrf) => a.id === b.id,
|
||||
(a: TDeviceVrf, b: TDeviceVrf) => a._id === b._id,
|
||||
);
|
||||
|
||||
availVrfs.set(intersecting);
|
||||
|
||||
// If there are no intersecting VRFs, use the default VRF.
|
||||
if (
|
||||
intersecting.filter(i => i.id === queryVrf.value).length === 0 &&
|
||||
queryVrf.value !== 'default'
|
||||
) {
|
||||
queryVrf.set('default');
|
||||
if (intersecting.filter(i => i._id === queryVrf.value).length === 0) {
|
||||
queryVrf.set('__hyperglass_default');
|
||||
}
|
||||
|
||||
// Determine which address families are available in the intersecting VRFs.
|
||||
|
@@ -2,7 +2,7 @@ import { Box, Stack, useToken } from '@chakra-ui/react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Label } from '~/components';
|
||||
import { useConfig, useBreakpointValue } from '~/context';
|
||||
import { useLGState } from '~/hooks';
|
||||
import { useLGState, useVrf } from '~/hooks';
|
||||
import { isQueryType } from '~/types';
|
||||
|
||||
import type { Transition } from 'framer-motion';
|
||||
@@ -10,7 +10,7 @@ import type { Transition } from 'framer-motion';
|
||||
const transition = { duration: 0.3, delay: 0.5 } as Transition;
|
||||
|
||||
export const Tags: React.FC = () => {
|
||||
const { queries, vrfs, web } = useConfig();
|
||||
const { queries, web } = useConfig();
|
||||
const { queryLocation, queryTarget, queryType, queryVrf } = useLGState();
|
||||
|
||||
const targetBg = useToken('colors', 'teal.600');
|
||||
@@ -64,8 +64,8 @@ export const Tags: React.FC = () => {
|
||||
queryTypeLabel = queries[queryType.value].display_name;
|
||||
}
|
||||
|
||||
const matchedVrf =
|
||||
vrfs.filter(v => v.id === queryVrf.value)[0] ?? vrfs.filter(v => v.id === 'default')[0];
|
||||
const getVrf = useVrf();
|
||||
const vrf = getVrf(queryVrf.value);
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -115,7 +115,7 @@ export const Tags: React.FC = () => {
|
||||
<Label
|
||||
bg={vrfBg}
|
||||
label={web.text.query_vrf}
|
||||
value={matchedVrf.display_name}
|
||||
value={vrf.display_name}
|
||||
fontSize={{ base: 'xs', md: 'sm' }}
|
||||
/>
|
||||
</motion.div>
|
||||
|
@@ -9,3 +9,4 @@ export * from './useLGState';
|
||||
export * from './useOpposingColor';
|
||||
export * from './useStrf';
|
||||
export * from './useTableToString';
|
||||
export * from './useVrf';
|
||||
|
@@ -50,6 +50,8 @@ export type TUseDevice = (
|
||||
deviceId: string,
|
||||
) => TDevice;
|
||||
|
||||
export type TUseVrf = (vrfId: string) => TDeviceVrf;
|
||||
|
||||
export interface TSelections {
|
||||
queryLocation: TSelectOption[] | [];
|
||||
queryType: TSelectOption | null;
|
||||
|
33
hyperglass/ui/hooks/useVrf.ts
Normal file
33
hyperglass/ui/hooks/useVrf.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useConfig } from '~/context';
|
||||
|
||||
import type { TDeviceVrf } from '~/types';
|
||||
import type { TUseVrf } from './types';
|
||||
|
||||
/**
|
||||
* Get a VRF configuration from the global configuration context based on its name.
|
||||
*/
|
||||
export function useVrf(): TUseVrf {
|
||||
const { networks } = useConfig();
|
||||
|
||||
const vrfs = useMemo(() => networks.map(n => n.locations.map(l => l.vrfs).flat()).flat(), []);
|
||||
|
||||
function getVrf(id: string): TDeviceVrf {
|
||||
const matching = vrfs.find(vrf => vrf._id === id);
|
||||
if (typeof matching === 'undefined') {
|
||||
if (id === '__hyperglass_default') {
|
||||
const anyDefault = vrfs.find(vrf => vrf.default === true);
|
||||
if (typeof anyDefault !== 'undefined') {
|
||||
return anyDefault;
|
||||
} else {
|
||||
throw new Error(`No matching VRF found for '${id}'`);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`No matching VRF found for '${id}'`);
|
||||
}
|
||||
}
|
||||
return matching;
|
||||
}
|
||||
|
||||
return useCallback(getVrf, []);
|
||||
}
|
@@ -105,8 +105,9 @@ export interface TConfigQueries {
|
||||
}
|
||||
|
||||
interface TDeviceVrfBase {
|
||||
id: string;
|
||||
_id: string;
|
||||
display_name: string;
|
||||
default: boolean;
|
||||
}
|
||||
|
||||
export interface TDeviceVrf extends TDeviceVrfBase {
|
||||
|
Reference in New Issue
Block a user